invar-tools 1.17.25__py3-none-any.whl → 1.17.27__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.
Files changed (36) hide show
  1. invar/core/contracts.py +38 -6
  2. invar/core/doc_edit.py +22 -9
  3. invar/core/doc_parser.py +17 -11
  4. invar/core/entry_points.py +48 -42
  5. invar/core/extraction.py +25 -9
  6. invar/core/format_specs.py +7 -2
  7. invar/core/format_strategies.py +6 -4
  8. invar/core/formatter.py +9 -6
  9. invar/core/hypothesis_strategies.py +24 -5
  10. invar/core/lambda_helpers.py +2 -2
  11. invar/core/parser.py +40 -21
  12. invar/core/patterns/detector.py +25 -6
  13. invar/core/patterns/p0_exhaustive.py +5 -5
  14. invar/core/patterns/p0_literal.py +8 -8
  15. invar/core/patterns/p0_newtype.py +2 -2
  16. invar/core/patterns/p0_nonempty.py +12 -7
  17. invar/core/patterns/p0_validation.py +4 -8
  18. invar/core/patterns/registry.py +12 -2
  19. invar/core/property_gen.py +47 -23
  20. invar/core/shell_analysis.py +70 -66
  21. invar/core/strategies.py +14 -3
  22. invar/core/suggestions.py +12 -4
  23. invar/core/tautology.py +33 -10
  24. invar/core/template_parser.py +23 -15
  25. invar/core/ts_parsers.py +6 -2
  26. invar/core/ts_sig_parser.py +18 -10
  27. invar/core/utils.py +38 -12
  28. invar/shell/property_tests.py +176 -48
  29. invar/templates/protocol/python/tools.md +3 -0
  30. {invar_tools-1.17.25.dist-info → invar_tools-1.17.27.dist-info}/METADATA +1 -1
  31. {invar_tools-1.17.25.dist-info → invar_tools-1.17.27.dist-info}/RECORD +36 -36
  32. {invar_tools-1.17.25.dist-info → invar_tools-1.17.27.dist-info}/WHEEL +0 -0
  33. {invar_tools-1.17.25.dist-info → invar_tools-1.17.27.dist-info}/entry_points.txt +0 -0
  34. {invar_tools-1.17.25.dist-info → invar_tools-1.17.27.dist-info}/licenses/LICENSE +0 -0
  35. {invar_tools-1.17.25.dist-info → invar_tools-1.17.27.dist-info}/licenses/LICENSE-GPL +0 -0
  36. {invar_tools-1.17.25.dist-info → invar_tools-1.17.27.dist-info}/licenses/NOTICE +0 -0
@@ -7,13 +7,10 @@ Handles I/O and file scanning, returns Result[T, E].
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- import importlib.util
11
10
  import sys
11
+ import tomllib
12
12
  from contextlib import contextmanager, suppress
13
- from typing import TYPE_CHECKING
14
-
15
- if TYPE_CHECKING:
16
- from pathlib import Path
13
+ from pathlib import Path
17
14
 
18
15
  from returns.result import Failure, Result, Success
19
16
  from rich.console import Console
@@ -24,9 +21,98 @@ from invar.shell.subprocess_env import detect_project_venv, find_site_packages
24
21
  console = Console()
25
22
 
26
23
 
24
+ def _extract_src_dirs_from_paths(project_root: Path, paths: list[str]) -> set[str]:
25
+ """
26
+ Extract unique src directories from configured paths.
27
+
28
+ For monorepo structures like 'packages/pkg-a/src/pkg_a/core',
29
+ extracts 'packages/pkg-a/src' as the directory to add to sys.path.
30
+
31
+ Examples:
32
+ >>> from pathlib import Path
33
+ >>> root = Path("/project")
34
+ >>> paths = ["packages/a/src/pkg/core", "packages/b/src/pkg/shell"]
35
+ >>> # Returns src dirs (would check existence in real use)
36
+ >>> sorted(_extract_src_dirs_from_paths(root, paths)) # doctest: +SKIP
37
+ ['/project/packages/a/src', '/project/packages/b/src']
38
+ """
39
+ src_dirs: set[str] = set()
40
+ for p in paths:
41
+ parts = Path(p).parts
42
+ if "src" in parts:
43
+ idx = parts.index("src")
44
+ src_dir = project_root / Path(*parts[: idx + 1])
45
+ if src_dir.exists():
46
+ src_dirs.add(str(src_dir))
47
+ return src_dirs
48
+
49
+
50
+ # @shell_complexity: Config fallthrough requires checking 3 sources with error handling
51
+ def _get_invar_paths_from_config(project_root: Path) -> list[str]:
52
+ """
53
+ Get 'paths' from [tool.invar] config across all config locations.
54
+
55
+ Checks in priority order:
56
+ 1. pyproject.toml [tool.invar].paths
57
+ 2. invar.toml [invar].paths (root level)
58
+ 3. .invar/config.toml [invar].paths (root level)
59
+
60
+ Returns first found, or empty list.
61
+ """
62
+ # 1. pyproject.toml [tool.invar].paths
63
+ pyproject = project_root / "pyproject.toml"
64
+ if pyproject.exists():
65
+ try:
66
+ with pyproject.open("rb") as f:
67
+ data = tomllib.load(f)
68
+ paths = data.get("tool", {}).get("invar", {}).get("paths", [])
69
+ if paths:
70
+ return paths
71
+ except Exception:
72
+ pass
73
+
74
+ # 2. invar.toml [invar].paths (root level, since no [tool] wrapper)
75
+ invar_toml = project_root / "invar.toml"
76
+ if invar_toml.exists():
77
+ try:
78
+ with invar_toml.open("rb") as f:
79
+ data = tomllib.load(f)
80
+ # In invar.toml, paths could be at root or under [invar]
81
+ paths = data.get("paths", []) or data.get("invar", {}).get("paths", [])
82
+ if paths:
83
+ return paths
84
+ except Exception:
85
+ pass
86
+
87
+ # 3. .invar/config.toml
88
+ invar_config = project_root / ".invar" / "config.toml"
89
+ if invar_config.exists():
90
+ try:
91
+ with invar_config.open("rb") as f:
92
+ data = tomllib.load(f)
93
+ paths = data.get("paths", []) or data.get("invar", {}).get("paths", [])
94
+ if paths:
95
+ return paths
96
+ except Exception:
97
+ pass
98
+
99
+ return []
100
+
101
+
27
102
  # @shell_orchestration: Temporarily inject venv site-packages for module imports
103
+ # @shell_complexity: Monorepo support requires reading config and extracting src dirs
28
104
  @contextmanager
29
105
  def _inject_project_site_packages(project_root: Path):
106
+ """
107
+ Context manager that temporarily injects project dependencies into sys.path.
108
+
109
+ Supports:
110
+ - Standard layout: project_root/src
111
+ - Monorepo layout: paths from config (pyproject.toml, invar.toml, .invar/config.toml)
112
+ - Configured paths: extracted from core_paths/shell_paths in [tool.invar.guard]
113
+ """
114
+ from invar.shell.config import get_path_classification
115
+
30
116
  venv = detect_project_venv(project_root)
31
117
  site_packages = find_site_packages(venv) if venv is not None else None
32
118
 
@@ -34,13 +120,35 @@ def _inject_project_site_packages(project_root: Path):
34
120
  yield
35
121
  return
36
122
 
37
- src_dir = project_root / "src"
38
-
39
123
  added: list[str] = []
124
+
125
+ # 1. Read 'paths' from config (supports pyproject.toml, invar.toml, .invar/config.toml)
126
+ invar_paths = _get_invar_paths_from_config(project_root)
127
+ for p in invar_paths:
128
+ src_dir = project_root / p
129
+ if src_dir.exists():
130
+ src_dir_str = str(src_dir)
131
+ if src_dir_str not in added:
132
+ sys.path.insert(0, src_dir_str)
133
+ added.append(src_dir_str)
134
+
135
+ # 2. Extract src dirs from core_paths and shell_paths config
136
+ path_result = get_path_classification(project_root)
137
+ if isinstance(path_result, Success):
138
+ core_paths, shell_paths = path_result.unwrap()
139
+ src_dirs = _extract_src_dirs_from_paths(project_root, core_paths + shell_paths)
140
+ for src_dir_str in src_dirs:
141
+ if src_dir_str not in added:
142
+ sys.path.insert(0, src_dir_str)
143
+ added.append(src_dir_str)
144
+
145
+ # 3. Fallback to project_root/src (standard layout)
146
+ src_dir = project_root / "src"
40
147
  if src_dir.exists():
41
148
  src_dir_str = str(src_dir)
42
- sys.path.insert(0, src_dir_str)
43
- added.append(src_dir_str)
149
+ if src_dir_str not in added:
150
+ sys.path.insert(0, src_dir_str)
151
+ added.append(src_dir_str)
44
152
 
45
153
  site_packages_str = str(site_packages)
46
154
  sys.path.insert(0, site_packages_str)
@@ -218,64 +326,84 @@ def _accumulate_report(
218
326
  combined_report.errors.extend(file_report.errors)
219
327
 
220
328
 
329
+ # @shell_complexity: Path traversal logic for monorepo src detection
330
+ def _find_module_root(file_path: Path, project_root: Path | None) -> Path | None:
331
+ """
332
+ Find the module root directory (the directory that should be in sys.path).
333
+
334
+ For monorepo structures, finds the 'src' directory containing the file.
335
+ Falls back to project_root for standard layouts.
336
+
337
+ Examples:
338
+ >>> from pathlib import Path
339
+ >>> # Standard layout: project/src/pkg/module.py -> project/src
340
+ >>> # Monorepo: project/packages/a/src/pkg/module.py -> project/packages/a/src
341
+ """
342
+ if project_root is None:
343
+ return None
344
+
345
+ # Check if file is under a 'src' directory
346
+ try:
347
+ relative = file_path.relative_to(project_root)
348
+ parts = relative.parts
349
+
350
+ if "src" in parts:
351
+ idx = parts.index("src")
352
+ # Return the src directory itself
353
+ return project_root / Path(*parts[: idx + 1])
354
+ except ValueError:
355
+ pass
356
+
357
+ # Fallback: check if project_root/src exists and contains the file
358
+ src_dir = project_root / "src"
359
+ if src_dir.exists():
360
+ try:
361
+ file_path.relative_to(src_dir)
362
+ return src_dir
363
+ except ValueError:
364
+ pass
365
+
366
+ return project_root
367
+
368
+
221
369
  # @shell_complexity: BUG-57 fix requires package hierarchy setup for relative imports
222
370
  def _import_module_from_path(file_path: Path, project_root: Path | None = None) -> object | None:
223
371
  """
224
372
  Import a Python module from a file path.
225
373
 
226
374
  BUG-57: Properly handles relative imports by setting up package context.
375
+ Monorepo fix: Calculates module name relative to src directory, not project root.
227
376
 
228
377
  Returns None if import fails.
229
378
  """
379
+ import importlib
380
+
230
381
  try:
231
- # Calculate the full module name from project root
232
- if project_root and file_path.is_relative_to(project_root):
233
- # Convert path to module name: my_package/main.py -> my_package.main
382
+ # Find the module root (src directory) for this file
383
+ module_root = _find_module_root(file_path, project_root)
384
+
385
+ # Calculate module name relative to module_root (not project_root!)
386
+ if module_root and file_path.is_relative_to(module_root):
387
+ relative = file_path.relative_to(module_root)
388
+ parts = list(relative.with_suffix("").parts)
389
+ module_name = ".".join(parts)
390
+ elif project_root and file_path.is_relative_to(project_root):
391
+ # Fallback to project_root relative path
234
392
  relative = file_path.relative_to(project_root)
235
393
  parts = list(relative.with_suffix("").parts)
236
394
  module_name = ".".join(parts)
237
395
  else:
238
396
  module_name = file_path.stem
239
397
 
240
- # Ensure project root is in sys.path for relative imports
241
- if project_root:
242
- root_str = str(project_root)
398
+ # Ensure module root is in sys.path
399
+ if module_root:
400
+ root_str = str(module_root)
243
401
  if root_str not in sys.path:
244
402
  sys.path.insert(0, root_str)
245
403
 
246
- # For packages with relative imports, we need to set up parent packages first
247
- if "." in module_name:
248
- # Import parent packages first
249
- parts = module_name.split(".")
250
- for i in range(1, len(parts)):
251
- parent_name = ".".join(parts[:i])
252
- if parent_name not in sys.modules:
253
- parent_path = project_root / "/".join(parts[:i]) if project_root else None
254
- if parent_path and (parent_path / "__init__.py").exists():
255
- parent_spec = importlib.util.spec_from_file_location(
256
- parent_name,
257
- parent_path / "__init__.py",
258
- submodule_search_locations=[str(parent_path)],
259
- )
260
- if parent_spec and parent_spec.loader:
261
- parent_module = importlib.util.module_from_spec(parent_spec)
262
- sys.modules[parent_name] = parent_module
263
- parent_spec.loader.exec_module(parent_module)
264
-
265
- # Now import the target module
266
- spec = importlib.util.spec_from_file_location(
267
- module_name,
268
- file_path,
269
- submodule_search_locations=[str(file_path.parent)],
270
- )
271
- if spec is None or spec.loader is None:
272
- return None
273
-
274
- module = importlib.util.module_from_spec(spec)
275
- sys.modules[module_name] = module
276
-
277
- # Suppress output during import
278
- spec.loader.exec_module(module)
404
+ # Use importlib.import_module which correctly handles relative imports
405
+ # This is simpler and more reliable than manual spec loading
406
+ module = importlib.import_module(module_name)
279
407
  return module
280
408
 
281
409
  except Exception:
@@ -23,5 +23,8 @@ core_paths = ["src/myapp/core"] # Default: ["src/core", "core"]
23
23
  shell_paths = ["src/myapp/shell"] # Default: ["src/shell", "shell"]
24
24
  max_file_lines = 500 # Default: 500 (warning at 80%)
25
25
  max_function_lines = 50 # Default: 50
26
+ timeout_doctest = 60 # Default: 60s
27
+ timeout_crosshair = 300 # Default: 300s
28
+ timeout_hypothesis = 300 # Default: 300s
26
29
  # Doctest lines are excluded from size calculations
27
30
  ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invar-tools
3
- Version: 1.17.25
3
+ Version: 1.17.27
4
4
  Summary: AI-native software engineering tools with design-by-contract verification
5
5
  Project-URL: Homepage, https://github.com/tefx/invar
6
6
  Project-URL: Documentation, https://github.com/tefx/invar#readme
@@ -1,52 +1,52 @@
1
1
  invar/__init__.py,sha256=E-Mg9DG6qFzjP8D5TEkmy2ponvR99Yfn96RVJggAr64,1682
2
2
  invar/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  invar/core/__init__.py,sha256=01TgQ2bqTFV4VFdksfqXYPa2WUqo-DpUWUkEcIUXFb4,218
4
- invar/core/contracts.py,sha256=SOyF1KeJ6hrEwfQ09UzMt881OJKDXRbPTslKA6HzdKg,19085
5
- invar/core/doc_edit.py,sha256=kLBkSQMFiXKdZMlQYAy-pzld1f1p2UFKGpKVVJo8Nes,6690
6
- invar/core/doc_parser.py,sha256=yzZKzgr9myUiiRGtRidT2tubrs4-omtGlTwbUkSKhoM,18197
7
- invar/core/entry_points.py,sha256=1p6GRGTp9kA9spNkGKidFLlzLPheh6JO2XFb68Cr0sE,12209
8
- invar/core/extraction.py,sha256=mScqEMEEQdsd-Z0jx9g3scK6Z1vI9l-ESjggXPIWHZ4,6112
4
+ invar/core/contracts.py,sha256=xLCI3rMLmI2W1sTjWeVPx98v6eLSHfJPi2FBfjO_HhA,20586
5
+ invar/core/doc_edit.py,sha256=ZIfdMeKfqLDwA0rOn19WdgmuKgLXyZWnr12fJU5ABvs,6638
6
+ invar/core/doc_parser.py,sha256=LJ3ljIgJP-RpiGLRAGWyNJXB14Y4IOMwFAYrGVAx5eg,18294
7
+ invar/core/entry_points.py,sha256=WXfOFDhpOUl52rrL0EwBIUMr__9TPD7JfyUEMV4oDfk,12413
8
+ invar/core/extraction.py,sha256=hpzW7qsWkVlLNXOmPp1TB_fuP65D1q1JrkmcvQ5TbFo,6387
9
9
  invar/core/feedback.py,sha256=JhQf32y_Qutza8D2b5qX2U4fM--vtR3sBdV22KrVNY0,3246
10
- invar/core/format_specs.py,sha256=P299aRHFMXyow8STwsvaT6Bg2ALPs2wSy7SByiRZZ-A,5610
11
- invar/core/format_strategies.py,sha256=laWixu7AV2GdKNK886Eu-3NLHRdQT-hH_HOQdgIAjKI,5848
12
- invar/core/formatter.py,sha256=rCGZhMpl4dPLrztgKDkNtAvnv2vKfomyIHl_6fThuno,11293
13
- invar/core/hypothesis_strategies.py,sha256=_MfjG7KxkmJvuPsczr_1JayR_YmiDzU2jJ8fQPoKGgs,16517
10
+ invar/core/format_specs.py,sha256=cd9dyiux4nCYQzT6r3u3atwR8oEuaXizxyhR42Y-xZ8,5638
11
+ invar/core/format_strategies.py,sha256=qfybRDhFiyxFWruEAypph5J_nZLpRLO6sWAZLJIaq9o,5887
12
+ invar/core/formatter.py,sha256=m0jAbSjY5vTg_l7OW4qECOABtBFXLra9LD0CsGQMpLs,11498
13
+ invar/core/hypothesis_strategies.py,sha256=zP_QTpXuFXnznNTzMQdkFhqNExSu8TVNDimfMh-novg,16856
14
14
  invar/core/inspect.py,sha256=l1knohwpLRHSNySPUjyeBHJusnU0vYiQGj4dMVgQZIo,4381
15
- invar/core/lambda_helpers.py,sha256=Ap1y7N0wpgCgPHwrs2pd7zD9Qq4Ptfd2iTliprXIkME,6457
15
+ invar/core/lambda_helpers.py,sha256=cemVKI-9iUZrHYfNMiqDGv8vyUwnUQJu55WYSc_uDoU,6457
16
16
  invar/core/language.py,sha256=aGUcrq--eQtAjb5bYE40eFmDhRs_EbBNGQ1sBYgTdt0,2637
17
17
  invar/core/models.py,sha256=WLHCd9M8anw5mALTMboTjxt5PcXO7eJajiYA8b6FGHk,16881
18
18
  invar/core/must_use.py,sha256=7HnnbT53lb4dOT-1mL64pz0JbQYytuw4eejNVe7iWKY,5496
19
- invar/core/parser.py,sha256=ucVpGziVzUvbkXT1n_SgOrYdStDEcNBqLuRGqK3_M5g,9205
19
+ invar/core/parser.py,sha256=GJdgAn9Ys8xKmLtbEp6n-vgkxAm3WS5ihuLJQ60YUPY,9379
20
20
  invar/core/postcondition_scope.py,sha256=ykjVNqZZ1zItBmI7ebgmLW5vFGE-vpaLRTvSgWaJMgM,5245
21
- invar/core/property_gen.py,sha256=Pf9VpVdJFJmEkIx78HlW_Gn70cC8_sxEmW9gv-2Cifc,14342
21
+ invar/core/property_gen.py,sha256=XOGejiVqiEpGrghsagicL2WaFPnBYsy1yxWTcPPDi8g,14557
22
22
  invar/core/purity.py,sha256=dt5dFy5V8Ch93iBJF5OuKUr1jjfimfY3oHLQD8KmLHw,12036
23
23
  invar/core/purity_heuristics.py,sha256=vsgphC1XPIFtsoLB0xvp--AyaJHqlh83LyKXYda4pWc,4546
24
24
  invar/core/references.py,sha256=64yGIdj9vL72Y4uUhJsi9pztZkuMnLN-7OcOziyxYMo,6339
25
25
  invar/core/review_trigger.py,sha256=4GGHUmgbVsQJAob4OO6A8G7KrLcNMwNOuqHiT6Jc7cs,14085
26
26
  invar/core/rule_meta.py,sha256=il_KUTjSlW1MOVgLguuLDS9wEdyqUe3CDvUx4gQjACo,10180
27
27
  invar/core/rules.py,sha256=y_NIpO8GAcw8WNPZjbJniu9FU7ofgfn3kZ_keexTqA8,23481
28
- invar/core/shell_analysis.py,sha256=i2A9SMqBI3Rb4Ai0QNTM7awIkSJIY6yZJVWS72lv0bY,6457
28
+ invar/core/shell_analysis.py,sha256=o1ogUvxzBlaupha88n17BUJwaV5vdcwD8UxpeSTyaKQ,6806
29
29
  invar/core/shell_architecture.py,sha256=98EVdBFIs8tO-i9jKuzdmv7fLB4PKnyI-vKh5lxnB98,6538
30
- invar/core/strategies.py,sha256=2DPl0z2p_CBNd4RlSbZzTeAy6Dq6cpCiBCB2p5qHHkk,8798
31
- invar/core/suggestions.py,sha256=LCg2Dy9EHh_n1t9jATRZ0gTkgJkAEZk3vp2nuuCyr-s,15129
30
+ invar/core/strategies.py,sha256=XXCoiK2FVmlIvDoED6aEEvbqKZyvKy78f4r5kELuPyQ,9041
31
+ invar/core/suggestions.py,sha256=ghN0YrXO_F21qyP8bHecQ9xKEIWZPXZ21VjBzLQnCig,15223
32
32
  invar/core/sync_helpers.py,sha256=rVfRFECO16Ntc9b9A7LxIV_0XfRJbRXaagVT-jJBYqI,8635
33
- invar/core/tautology.py,sha256=Pmn__a0Bt55W0lAQo1G5q8Ory9KuE23dRknKw45xxbs,9221
33
+ invar/core/tautology.py,sha256=IUC7H3OxWYlxofcl3I4ANgqaWQz5PL1WUzmJ9nzybMs,9463
34
34
  invar/core/template_helpers.py,sha256=E1UT7ct0DaUFlfHr9oTBvW4xfxAiS81rbmZHSucPw4c,881
35
- invar/core/template_parser.py,sha256=vH3H8OX55scZ1hWh3xoA8oJMhgleKufCOhkTvsSuu_4,14730
35
+ invar/core/template_parser.py,sha256=S-7M67JuUthhP_zoP8oAdPg6L-fFvO3pD8cS7KUVbes,14879
36
36
  invar/core/timeout_inference.py,sha256=BS2fJGmwOrLpYZUku4qrizgNDSIXVLFBslW-6sRAvpc,3451
37
37
  invar/core/trivial_detection.py,sha256=KYP8jJb7QDeusAxFdX5NAML_H0NL5wLgMeBWDQmNqfU,6086
38
- invar/core/ts_parsers.py,sha256=gXvLgb141gD8VAtiW4T1aaRnaSv10BEm0XYqArYIN00,8743
39
- invar/core/ts_sig_parser.py,sha256=_lUNfArFPILpENo-1dqmPY1qoVmcfAehv2Tva3r6dzw,9931
40
- invar/core/utils.py,sha256=PyW8dcTLUEFD81xcvkz-LNnCwjIQefn08OUh23fM_Po,14266
38
+ invar/core/ts_parsers.py,sha256=DTGig4qLNGBnRIM7FXAi2bYumjLf3iFK-VkfP9F7204,8817
39
+ invar/core/ts_sig_parser.py,sha256=ZB8PaxrN4ADVYd7ArPbpEJlnRIy79aPjyxjkJIKwmI8,10125
40
+ invar/core/utils.py,sha256=HMGB2w5B-4XhBHoOLaQkQ882a4vZFJ9deCy1qpgLrRc,14930
41
41
  invar/core/verification_routing.py,sha256=_jXi1txFCcUdnB3-Yavtuyk8N-XhEO_Vu_051Vuz27Y,5020
42
42
  invar/core/patterns/__init__.py,sha256=79a3ucN0BI54RnIOe49lngKASpADygs1hll9ROCrP6s,1429
43
- invar/core/patterns/detector.py,sha256=lUfED7qk2VWOAjHoGWgSqll5ynhhdzd6CjCiol-7kV8,8690
44
- invar/core/patterns/p0_exhaustive.py,sha256=66FNRRFAsOQh1LyxWULgkuM52CLtBWTVXlExpAVHMpo,7176
45
- invar/core/patterns/p0_literal.py,sha256=WkQNs4605PtBhLb5DKLXc1Nqlpezx5HjktC7JX6ltaM,10946
46
- invar/core/patterns/p0_newtype.py,sha256=DWI6zXRsV9YHSemEuCVFSTqWrB-ROl3xP86cPu9UC2w,7803
47
- invar/core/patterns/p0_nonempty.py,sha256=bJ97NqdFClYM9LaSiyViRt2qJrnT6kO9-ykBVbV20aE,10927
48
- invar/core/patterns/p0_validation.py,sha256=u_pQ1vskhIDLpu_xFlQfgsswZvi27s-cO8A7DTseAc0,9819
49
- invar/core/patterns/registry.py,sha256=2rz0wWDRarMkuHN-qM_ZrT3qeGFDSKMABvRvPNZxQNw,7495
43
+ invar/core/patterns/detector.py,sha256=mwUDJ0Ha930BnRYF-ydbNCIF6ndQFwkHn055X1xy-OE,9107
44
+ invar/core/patterns/p0_exhaustive.py,sha256=ykbObko9bC3BQaaCNVhUk1nt29CvqOcAjXQa_ZAkFho,7229
45
+ invar/core/patterns/p0_literal.py,sha256=jp-3uVKwTGn5XgX_kz_wYRrtHi27oIrLvwT_MpXjY7A,10951
46
+ invar/core/patterns/p0_newtype.py,sha256=iKlErz697lXrTCFkMKU8yn4FiTAIkP8fCTTs6kJ_6BA,7846
47
+ invar/core/patterns/p0_nonempty.py,sha256=Ipe5eIKBBXN6w-a3h0bawigw5lEMrxUF_9CYPV9YDuU,11151
48
+ invar/core/patterns/p0_validation.py,sha256=Ri_L9StWCpL2C_akn0QQudsRhrU51gEUtR3PHBhZBjA,9833
49
+ invar/core/patterns/registry.py,sha256=4SPMgP9uutNTvvys8pUru41ibLNhR1mGHtQHxwKNhFw,7794
50
50
  invar/core/patterns/types.py,sha256=ULAlWuAdmO6CFcEDjTrWBfzNTBsnomAl2d25tR11ihU,5506
51
51
  invar/mcp/__init__.py,sha256=n3S7QwMjSMqOMT8cI2jf9E0yZPjKmBOJyIYhq4WZ8TQ,226
52
52
  invar/mcp/__main__.py,sha256=ZcIT2U6xUyGOWucl4jq422BDE3lRLjqyxb9pFylRBdk,219
@@ -2670,7 +2670,7 @@ invar/shell/mutation.py,sha256=Lfyk2b8j8-hxAq-iwAgQeOhr7Ci6c5tRF1TXe3CxQCs,8914
2670
2670
  invar/shell/pattern_integration.py,sha256=pRcjfq3NvMW_tvQCnaXZnD1k5AVEWK8CYOE2jN6VTro,7842
2671
2671
  invar/shell/pi_hooks.py,sha256=ulZc1sP8mTRJTBsjwFHQzUgg-h8ajRIMp7iF1Y4UUtw,6885
2672
2672
  invar/shell/pi_tools.py,sha256=a3ACDDXykFV8fUB5UpBmgMvppwkmLvT1k_BWm0IY47k,4068
2673
- invar/shell/property_tests.py,sha256=qt0CP5RH9Md2ZZV64ziNsjQ_-x0onCYtZwbQfqw9gbY,12586
2673
+ invar/shell/property_tests.py,sha256=zKkCh03MaWTLnz_gfdTMeCGb2RMK_sz0L8PAcD8-iVU,17031
2674
2674
  invar/shell/py_refs.py,sha256=Vjz50lmt9prDBcBv4nkkODdiJ7_DKu5zO4UPZBjAfmM,4638
2675
2675
  invar/shell/skill_manager.py,sha256=Mr7Mh9rxPSKSAOTJCAM5ZHiG5nfUf6KQVCuD4LBNHSI,12440
2676
2676
  invar/shell/subprocess_env.py,sha256=hendEERSyAG4a8UFhYfPtOAlfspVRB03aVCYpj3uqk4,12745
@@ -2745,7 +2745,7 @@ invar/templates/protocol/INVAR.md.jinja,sha256=t2ZIQZJvzDTJMrRw_ijUo6ScZmeNK0-nV
2745
2745
  invar/templates/protocol/python/architecture-examples.md,sha256=O96LH9WFpk7G9MrhSbifLS5pyibTIDG-_EGFF7g3V4M,1175
2746
2746
  invar/templates/protocol/python/contracts-syntax.md,sha256=Q6supTQ3tChVrlN7xhcdb3Q8VGIESxQLA-mQvrNIZmo,1162
2747
2747
  invar/templates/protocol/python/markers.md,sha256=fzltCKbdPVz_vCuJFiQ9pbRPztvpMJpSf_4aFHcXFLM,1223
2748
- invar/templates/protocol/python/tools.md,sha256=RULbZhisRxNI1oTxYzhWI3NTVyKNukrkQbwR5XWQt2Y,1043
2748
+ invar/templates/protocol/python/tools.md,sha256=qdKRRJok_ZU1z0B8SV9huhRED8qFRGaICmuWNZkwrCM,1201
2749
2749
  invar/templates/protocol/python/troubleshooting.md,sha256=-JHLUOxvfQeSrLpqKrxUXQ5UrkW44AHFr3LGHwxnw7w,1081
2750
2750
  invar/templates/protocol/typescript/architecture-examples.md,sha256=Dej-DI6OqRVsbzjukjOqdO8WEz0aT-1iwYqrah2B_xk,1454
2751
2751
  invar/templates/protocol/typescript/contracts-syntax.md,sha256=yKyM6WhyF5p-bt-RqD5SI4ZZudE7bLLFTAMzVSa74QE,1610
@@ -2778,10 +2778,10 @@ invar/templates/skills/invar-reflect/template.md,sha256=Rr5hvbllvmd8jSLf_0ZjyKt6
2778
2778
  invar/templates/skills/investigate/SKILL.md.jinja,sha256=cp6TBEixBYh1rLeeHOR1yqEnFqv1NZYePORMnavLkQI,3231
2779
2779
  invar/templates/skills/propose/SKILL.md.jinja,sha256=6BuKiCqO1AEu3VtzMHy1QWGqr_xqG9eJlhbsKT4jev4,3463
2780
2780
  invar/templates/skills/review/SKILL.md.jinja,sha256=ET5mbdSe_eKgJbi2LbgFC-z1aviKcHOBw7J5Q28fr4U,14105
2781
- invar_tools-1.17.25.dist-info/METADATA,sha256=t7Kc8ideD0woPDbz-PQiXfyQzlyBOPis0VZjDQHHCPU,28582
2782
- invar_tools-1.17.25.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
2783
- invar_tools-1.17.25.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
2784
- invar_tools-1.17.25.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
2785
- invar_tools-1.17.25.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
2786
- invar_tools-1.17.25.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
2787
- invar_tools-1.17.25.dist-info/RECORD,,
2781
+ invar_tools-1.17.27.dist-info/METADATA,sha256=lA_4PuEF0Dloh5LYtlMf_0vNvyicErt7xJYEeoioQZA,28582
2782
+ invar_tools-1.17.27.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
2783
+ invar_tools-1.17.27.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
2784
+ invar_tools-1.17.27.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
2785
+ invar_tools-1.17.27.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
2786
+ invar_tools-1.17.27.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
2787
+ invar_tools-1.17.27.dist-info/RECORD,,