invar-tools 1.15.6__py3-none-any.whl → 1.17.0__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.
@@ -84,6 +84,46 @@ class TypeScriptGuardResult:
84
84
  enhanced: EnhancedAnalysis | None = None
85
85
 
86
86
 
87
+ # DX-22: Fix-or-Explain complexity debt enforcement
88
+ # @shell_orchestration: Checks project-level complexity debt
89
+ def check_ts_complexity_debt(
90
+ violations: list[TypeScriptViolation], limit: int = 3
91
+ ) -> list[TypeScriptViolation]:
92
+ """Check project-level shell-complexity debt (DX-22 Fix-or-Explain).
93
+
94
+ When the project has too many unaddressed shell-complexity warnings,
95
+ escalate to ERROR to force resolution. TypeScript default is 3 (vs Python's 5)
96
+ since TS projects tend to be smaller.
97
+
98
+ Args:
99
+ violations: All violations from ESLint/tsc/vitest.
100
+ limit: Maximum unaddressed complexity warnings before ERROR (default 3).
101
+
102
+ Returns:
103
+ List with single ERROR violation if debt limit exceeded, empty list otherwise.
104
+ """
105
+ # Count unaddressed shell-complexity warnings
106
+ unaddressed = [
107
+ v
108
+ for v in violations
109
+ if v.rule == "@invar/shell-complexity" and v.severity == "warning"
110
+ ]
111
+
112
+ if len(unaddressed) >= limit:
113
+ return [
114
+ TypeScriptViolation(
115
+ file="<project>",
116
+ line=None,
117
+ column=None,
118
+ rule="@invar/shell-complexity-debt",
119
+ message=f"Project has {len(unaddressed)} unaddressed complexity warnings (limit: {limit})",
120
+ severity="error",
121
+ source="eslint",
122
+ )
123
+ ]
124
+ return []
125
+
126
+
87
127
  # @shell_orchestration: Transforms TypeScriptGuardResult to JSON for agent consumption
88
128
  def format_typescript_guard_v2(result: TypeScriptGuardResult) -> dict:
89
129
  """Format TypeScript guard result as v2.0 JSON.
@@ -403,8 +443,32 @@ def run_ts_analyzer(project_path: Path) -> Result[dict, str]:
403
443
  # ts-analyzer exits non-zero when critical blind spots found, but still outputs valid JSON
404
444
  # Try to parse JSON output regardless of exit code
405
445
  if result.stdout.strip():
406
- with contextlib.suppress(json.JSONDecodeError):
407
- return Success(json.loads(result.stdout))
446
+ try:
447
+ data = json.loads(result.stdout)
448
+ return Success(data)
449
+ except json.JSONDecodeError as e:
450
+ # JSON too large or malformed - try to extract summary from text output
451
+ # Fall back to running without --json flag for human-readable summary
452
+ try:
453
+ summary_result = subprocess.run(
454
+ [*cmd, str(project_path)],
455
+ capture_output=True,
456
+ text=True,
457
+ timeout=60,
458
+ cwd=project_path,
459
+ )
460
+ # Extract key metrics from text output
461
+ output = summary_result.stdout
462
+ coverage_match = re.search(r'Contract coverage: (\d+)%', output)
463
+ coverage = int(coverage_match.group(1)) if coverage_match else None
464
+
465
+ return Success({
466
+ "coverage": coverage,
467
+ "summary_mode": True,
468
+ "note": "Full JSON output too large, using summary metrics"
469
+ })
470
+ except Exception:
471
+ return Failure(f"JSON parse error: {str(e)[:100]}")
408
472
 
409
473
  # Only report failure if no valid JSON output
410
474
  if "not found" in result.stderr.lower() or "ENOENT" in result.stderr:
@@ -664,7 +728,9 @@ def _parse_tsc_line(line: str) -> TypeScriptViolation | None:
664
728
 
665
729
  # @shell_complexity: CLI tool integration with JSON parsing and error handling
666
730
  def run_eslint(project_path: Path) -> Result[list[TypeScriptViolation], str]:
667
- """Run ESLint for code quality checks.
731
+ """Run ESLint with @invar/eslint-plugin rules.
732
+
733
+ Uses @invar/eslint-plugin CLI which pre-loads Invar-specific rules.
668
734
 
669
735
  Args:
670
736
  project_path: Path to project root.
@@ -677,9 +743,12 @@ def run_eslint(project_path: Path) -> Result[list[TypeScriptViolation], str]:
677
743
  return Failure(f"Project path does not exist: {project_path}")
678
744
 
679
745
  try:
746
+ # Get command for @invar/eslint-plugin (embedded or local dev)
747
+ cmd = _get_invar_package_cmd("eslint-plugin", project_path)
748
+ cmd.append(str(project_path)) # Add project path as argument
749
+
680
750
  result = subprocess.run(
681
- ["npx", "eslint", ".", "--format", "json", "--ext", ".ts,.tsx"],
682
- cwd=project_path,
751
+ cmd,
683
752
  capture_output=True,
684
753
  text=True,
685
754
  timeout=120,
@@ -745,9 +814,27 @@ def run_vitest(project_path: Path) -> Result[list[TypeScriptViolation], str]:
745
814
  deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
746
815
  if "vitest" not in deps:
747
816
  return Success([]) # No vitest configured, skip
748
- except json.JSONDecodeError:
817
+ except (json.JSONDecodeError, OSError):
818
+ # Handle both JSON parse errors and IO errors (file deleted, permission denied, etc.)
749
819
  pass
750
820
 
821
+ # LX-15 Phase 1: Generate doctests before running vitest
822
+ try:
823
+ doctest_result = subprocess.run(
824
+ ["node", "scripts/generate-doctests.mjs", "doctest.config.json"],
825
+ cwd=project_path,
826
+ capture_output=True,
827
+ text=True,
828
+ timeout=60,
829
+ )
830
+ # Doctest generation failure is not fatal - continue with regular tests
831
+ if doctest_result.returncode != 0:
832
+ # Log warning but don't fail
833
+ pass
834
+ except (FileNotFoundError, subprocess.TimeoutExpired):
835
+ # Doctest generation not available or timed out - continue with regular tests
836
+ pass
837
+
751
838
  try:
752
839
  result = subprocess.run(
753
840
  ["npx", "vitest", "run", "--reporter=json"],
@@ -838,8 +925,19 @@ def run_typescript_guard(
838
925
  match eslint_result:
839
926
  case Success(violations):
840
927
  all_violations.extend(violations)
841
- case Failure(_):
842
- pass # ESLint errors are non-fatal
928
+ case Failure(err):
929
+ # Report ESLint failure as a violation instead of silently ignoring
930
+ all_violations.append(
931
+ TypeScriptViolation(
932
+ file="<eslint>",
933
+ line=0,
934
+ column=0,
935
+ rule="eslint-error",
936
+ message=f"ESLint failed to run: {err}",
937
+ severity="error",
938
+ source="eslint",
939
+ )
940
+ )
843
941
 
844
942
  # Run vitest
845
943
  if result.vitest_available and not skip_tests:
@@ -889,6 +987,10 @@ def run_typescript_guard(
889
987
 
890
988
  result.enhanced = enhanced
891
989
 
990
+ # DX-22: Check for complexity debt (project-level Fix-or-Explain enforcement)
991
+ complexity_debt_violations = check_ts_complexity_debt(all_violations, limit=3)
992
+ all_violations.extend(complexity_debt_violations)
993
+
892
994
  # Aggregate results
893
995
  result.violations = all_violations
894
996
  result.error_count = sum(1 for v in all_violations if v.severity == "error")
invar/shell/templates.py CHANGED
@@ -11,7 +11,12 @@ from pathlib import Path
11
11
 
12
12
  from returns.result import Failure, Result, Success
13
13
 
14
- _DEFAULT_PYPROJECT_CONFIG = """\n# Invar Configuration
14
+ # =============================================================================
15
+ # Language-Specific Configurations (LX-05)
16
+ # =============================================================================
17
+
18
+ # Python configuration
19
+ _PYTHON_PYPROJECT_CONFIG = """\n# Invar Configuration
15
20
  [tool.invar.guard]
16
21
  core_paths = ["src/core"]
17
22
  shell_paths = ["src/shell"]
@@ -23,7 +28,7 @@ forbidden_imports = ["os", "sys", "socket", "requests", "urllib", "subprocess",
23
28
  exclude_paths = ["tests", "test", "scripts", ".venv", "venv", "__pycache__", ".pytest_cache", "node_modules", "dist", "build"]
24
29
  """
25
30
 
26
- _DEFAULT_INVAR_TOML = """# Invar Configuration
31
+ _PYTHON_INVAR_TOML = """# Invar Configuration (Python)
27
32
  # For projects without pyproject.toml
28
33
 
29
34
  [guard]
@@ -41,6 +46,37 @@ exclude_paths = ["tests", "test", "scripts", ".venv", "venv", "__pycache__", ".p
41
46
  # shell_patterns = ["**/api/**", "**/cli/**"]
42
47
  """
43
48
 
49
+ # TypeScript configuration (LX-05)
50
+ _TYPESCRIPT_INVAR_TOML = """# Invar Configuration (TypeScript)
51
+ # For TypeScript/JavaScript projects
52
+
53
+ [guard]
54
+ core_paths = ["src/core"]
55
+ shell_paths = ["src/shell"]
56
+ max_file_lines = 500
57
+ max_function_lines = 50
58
+ require_contracts = true
59
+ require_doctests = false # TypeScript uses JSDoc examples instead
60
+ # TypeScript/Node.js I/O modules to forbid in Core
61
+ forbidden_imports = ["fs", "path", "http", "https", "net", "child_process", "os", "process"]
62
+ exclude_paths = ["tests", "test", "scripts", "node_modules", "dist", "build", ".next", "coverage"]
63
+
64
+ # Pattern-based classification (optional, takes priority over paths)
65
+ # core_patterns = ["**/domain/**", "**/models/**"]
66
+ # shell_patterns = ["**/api/**", "**/cli/**"]
67
+ """
68
+
69
+ # Backward compatibility alias
70
+ _DEFAULT_PYPROJECT_CONFIG = _PYTHON_PYPROJECT_CONFIG
71
+ _DEFAULT_INVAR_TOML = _PYTHON_INVAR_TOML
72
+
73
+
74
+ def _get_invar_config(language: str) -> str:
75
+ """Get the appropriate config content for the language."""
76
+ if language == "typescript":
77
+ return _TYPESCRIPT_INVAR_TOML
78
+ return _PYTHON_INVAR_TOML
79
+
44
80
 
45
81
  def get_template_path(name: str) -> Result[Path, str]:
46
82
  """Get path to a template file."""
@@ -75,10 +111,11 @@ def copy_template(
75
111
 
76
112
 
77
113
  # @shell_complexity: Config addition with existing file detection
78
- def add_config(path: Path, console) -> Result[bool, str]:
114
+ def add_config(path: Path, console, language: str = "python") -> Result[bool, str]:
79
115
  """Add configuration to project. Returns Success(True) if added, Success(False) if skipped.
80
116
 
81
117
  DX-70: Creates .invar/config.toml instead of invar.toml for cleaner organization.
118
+ LX-05: Now generates language-specific config (Python vs TypeScript).
82
119
  Backward compatible: still reads from invar.toml if it exists.
83
120
  """
84
121
  pyproject = path / "pyproject.toml"
@@ -87,8 +124,8 @@ def add_config(path: Path, console) -> Result[bool, str]:
87
124
  legacy_invar_toml = path / "invar.toml"
88
125
 
89
126
  try:
90
- # Priority 1: Add to pyproject.toml if it exists
91
- if pyproject.exists():
127
+ # Priority 1: Add to pyproject.toml if it exists (Python projects only)
128
+ if pyproject.exists() and language == "python":
92
129
  content = pyproject.read_text()
93
130
  if "[tool.invar]" not in content:
94
131
  with pyproject.open("a") as f:
@@ -102,9 +139,10 @@ def add_config(path: Path, console) -> Result[bool, str]:
102
139
  return Success(False)
103
140
 
104
141
  # Create .invar/config.toml (DX-70: new default location)
142
+ # LX-05: Use language-specific config
105
143
  if not invar_config.exists():
106
144
  invar_dir.mkdir(exist_ok=True)
107
- invar_config.write_text(_DEFAULT_INVAR_TOML)
145
+ invar_config.write_text(_get_invar_config(language))
108
146
  console.print("[green]Created[/green] .invar/config.toml")
109
147
  return Success(True)
110
148
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invar-tools
3
- Version: 1.15.6
3
+ Version: 1.17.0
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
@@ -8,7 +8,7 @@ invar/core/entry_points.py,sha256=1p6GRGTp9kA9spNkGKidFLlzLPheh6JO2XFb68Cr0sE,12
8
8
  invar/core/extraction.py,sha256=mScqEMEEQdsd-Z0jx9g3scK6Z1vI9l-ESjggXPIWHZ4,6112
9
9
  invar/core/feedback.py,sha256=JhQf32y_Qutza8D2b5qX2U4fM--vtR3sBdV22KrVNY0,3246
10
10
  invar/core/format_specs.py,sha256=P299aRHFMXyow8STwsvaT6Bg2ALPs2wSy7SByiRZZ-A,5610
11
- invar/core/format_strategies.py,sha256=LifL97JbsF8WEkVNmQpq2htyFUC3pW21myAjtRGpSxU,5774
11
+ invar/core/format_strategies.py,sha256=laWixu7AV2GdKNK886Eu-3NLHRdQT-hH_HOQdgIAjKI,5848
12
12
  invar/core/formatter.py,sha256=rCGZhMpl4dPLrztgKDkNtAvnv2vKfomyIHl_6fThuno,11293
13
13
  invar/core/hypothesis_strategies.py,sha256=_MfjG7KxkmJvuPsczr_1JayR_YmiDzU2jJ8fQPoKGgs,16517
14
14
  invar/core/inspect.py,sha256=l1knohwpLRHSNySPUjyeBHJusnU0vYiQGj4dMVgQZIo,4381
@@ -50,12 +50,22 @@ invar/core/patterns/registry.py,sha256=2rz0wWDRarMkuHN-qM_ZrT3qeGFDSKMABvRvPNZxQ
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
53
- invar/mcp/handlers.py,sha256=b0LRWKMpu6lnjPV7SozH0cxKYJufVWy3eJhiAu3of-M,15947
53
+ invar/mcp/handlers.py,sha256=Kls1aXnYmGcMvib_Mesfz5FjBaL7lmrhKaw_P2JDnyw,16551
54
54
  invar/mcp/server.py,sha256=WmVNs2_AcOMbmp3tEgWR57hjqqJkMx3nV3z9fcNuSAA,20109
55
- invar/node_tools/MANIFEST,sha256=UwtO2AsQ-0UwskG6ZkE2kXqz_hdp-gzRTyp32-X22Mc,131
55
+ invar/node_tools/.gitignore,sha256=Tu_FtBhQTKPl9LHokxUTCTcxPadynDy6VPo0Z1WCBAQ,388
56
+ invar/node_tools/MANIFEST,sha256=2Z2at-27MK8K7DSjOjjtR4faTbt6eCiKQuEfvP_lwH8,145
56
57
  invar/node_tools/__init__.py,sha256=HzILh3jtP28Lm2jZwss1SY65ECxbtw2J2uFpXQA6Y94,1740
57
58
  invar/node_tools/ts-query.js,sha256=fEc_f0JT_Mb18dEoA4_vJoazvd7Lqv_rsed4eHSAbCg,13303
58
- invar/node_tools/fc-runner/cli.js,sha256=72_gIhvnx2peKITdzdnFWI5fzGaNTS3BcEqyS628cI0,243277
59
+ invar/node_tools/eslint-plugin/cli.js,sha256=U2BK7c5fx6J3XVMIAxW6tz3jxUwzqjuXP9hCY0KxctM,6752
60
+ invar/node_tools/fc-runner/bundle.js,sha256=D1wDqXYDOwWU4eqXeZdLIFvSsJ36q56Ay_ipLdklQPw,243285
61
+ invar/node_tools/fc-runner/cli.d.ts,sha256=UM1WM4OKVvABXIsTHt6VHdO_oQMlgltUFV6CIqeh1B0,294
62
+ invar/node_tools/fc-runner/cli.d.ts.map,sha256=-R1m4uJf2vmDZEpU7pEaib6TVjd0YuQnTQEGwkN6I9Q,117
63
+ invar/node_tools/fc-runner/cli.js,sha256=8T_YWJMmWEQmbCFsG8r4R03mi5qGpkfhitsh_5o455c,5007
64
+ invar/node_tools/fc-runner/cli.js.map,sha256=G4ecWIoU7P1ME12tM09_4N_Y6tppFNlrL3IWNPwKR2g,4195
65
+ invar/node_tools/fc-runner/index.d.ts,sha256=ejHHCxatrAbDSHWuHsedGHLnpLNa6ek7rowZLdf_NiY,12615
66
+ invar/node_tools/fc-runner/index.d.ts.map,sha256=MK73QKaDi8IhZ7Diphvs2sYNYbmpNIobXHbUNQBaKNg,1699
67
+ invar/node_tools/fc-runner/index.js,sha256=A0ard14fRX6ZnKjtONPGyE1EUMuRN9K-InCPwGIRuPU,8782
68
+ invar/node_tools/fc-runner/index.js.map,sha256=2ASF8aJUvuYqBgnfL0rxw4xRSK6ur23G0HGn5w0IQZI,6754
59
69
  invar/node_tools/quick-check/cli.js,sha256=dwV3hdJleFQga2cKUn3PPfQDvvujhzKdjQcIvWsKgM0,66196
60
70
  invar/node_tools/ts-analyzer/cli.js,sha256=SvZ6HyjmobpP8NAZqXFiy8BwH_t5Hb17Ytar_18udaQ,4092887
61
71
  invar/shell/__init__.py,sha256=FFw1mNbh_97PeKPcHIqQpQ7mw-JoIvyLM1yOdxLw5uk,204
@@ -78,7 +88,7 @@ invar/shell/py_refs.py,sha256=Vjz50lmt9prDBcBv4nkkODdiJ7_DKu5zO4UPZBjAfmM,4638
78
88
  invar/shell/skill_manager.py,sha256=Mr7Mh9rxPSKSAOTJCAM5ZHiG5nfUf6KQVCuD4LBNHSI,12440
79
89
  invar/shell/subprocess_env.py,sha256=9oXl3eMEbzLsFEgMHqobEw6oW_wV0qMEP7pklwm58Pw,11453
80
90
  invar/shell/template_engine.py,sha256=eNKMz7R8g9Xp3_1TGx-QH137jf52E0u3KaVcnotu1Tg,12056
81
- invar/shell/templates.py,sha256=31f5ieoGeWU0qqfLJUMWnz0yyLa1FBc_sOz6UGzToqk,13884
91
+ invar/shell/templates.py,sha256=ilhGysbUcdkUFqPgv6ySVmKI3imS_cwYNCWTCdyb5cY,15407
82
92
  invar/shell/testing.py,sha256=rTNBH0Okh2qtG9ohSXOz487baQ2gXrWT3s_WECW3HJs,11143
83
93
  invar/shell/ts_compiler.py,sha256=nA8brnOhThj9J_J3vAEGjDsM4NjbWQ_eX8Yf4pHPOgk,6672
84
94
  invar/shell/commands/__init__.py,sha256=MEkKwVyjI9DmkvBpJcuumXo2Pg_FFkfEr-Rr3nrAt7A,284
@@ -86,7 +96,7 @@ invar/shell/commands/doc.py,sha256=SOLDoCXXGxx_JU0PKXlAIGEF36PzconHmmAtL-rM6D4,1
86
96
  invar/shell/commands/feedback.py,sha256=lLxEeWW_71US_vlmorFrGXS8IARB9nbV6D0zruLs660,7640
87
97
  invar/shell/commands/guard.py,sha256=xTQ8cPp-x1xMCtufKxmMNUSpIpH31uUjziAB8ifCnC0,24837
88
98
  invar/shell/commands/hooks.py,sha256=W-SOnT4VQyUvXwipozkJwgEYfiOJGz7wksrbcdWegUg,2356
89
- invar/shell/commands/init.py,sha256=vaPo0p7xBm3Nfgu9ytcvAjgk4dQBKvyEhrz_Cg1URMQ,23557
99
+ invar/shell/commands/init.py,sha256=rtoPFsfq7xRZ6lfTipWT1OejNK5wfzqu1ncXi1kizU0,23634
90
100
  invar/shell/commands/merge.py,sha256=nuvKo8m32-OL-SCQlS4SLKmOZxQ3qj-1nGCx1Pgzifw,8183
91
101
  invar/shell/commands/mutate.py,sha256=GwemiO6LlbGCBEQsBFnzZuKhF-wIMEl79GAMnKUWc8U,5765
92
102
  invar/shell/commands/perception.py,sha256=Vl6zgxkqtS3QRXBat6U_utNhpViyPFoPh899-OtDLgQ,19493
@@ -100,7 +110,7 @@ invar/shell/prove/__init__.py,sha256=ZqlbmyMFJf6yAle8634jFuPRv8wNvHps8loMlOJyf8A
100
110
  invar/shell/prove/accept.py,sha256=cnY_6jzU1EBnpLF8-zWUWcXiSXtCwxPsXEYXsSVPG38,3717
101
111
  invar/shell/prove/cache.py,sha256=jbNdrvfLjvK7S0iqugErqeabb4YIbQuwIlcSRyCKbcg,4105
102
112
  invar/shell/prove/crosshair.py,sha256=XhJDsQWIriX9SuqeflUYvJgp9gJTDH7I7Uka6zjNzZ0,16734
103
- invar/shell/prove/guard_ts.py,sha256=M285vnaKGC3Dokyu7PrjlNk-sT1TP8H821fz5K4CL8c,31717
113
+ invar/shell/prove/guard_ts.py,sha256=4wY2QyyOXJ6eRjHWd6M4l866i3_o6AmfffFQVq7Q5xU,35886
104
114
  invar/shell/prove/hypothesis.py,sha256=QUclOOUg_VB6wbmHw8O2EPiL5qBOeBRqQeM04AVuLw0,9880
105
115
  invar/templates/CLAUDE.md.template,sha256=eaGU3SyRO_NEifw5b26k3srgQH4jyeujjCJ-HbM36_w,4913
106
116
  invar/templates/__init__.py,sha256=cb3ht8KPK5oBn5oG6HsTznujmo9WriJ_P--fVxJwycc,45
@@ -181,10 +191,10 @@ invar/templates/skills/invar-reflect/template.md,sha256=Rr5hvbllvmd8jSLf_0ZjyKt6
181
191
  invar/templates/skills/investigate/SKILL.md.jinja,sha256=cp6TBEixBYh1rLeeHOR1yqEnFqv1NZYePORMnavLkQI,3231
182
192
  invar/templates/skills/propose/SKILL.md.jinja,sha256=6BuKiCqO1AEu3VtzMHy1QWGqr_xqG9eJlhbsKT4jev4,3463
183
193
  invar/templates/skills/review/SKILL.md.jinja,sha256=ET5mbdSe_eKgJbi2LbgFC-z1aviKcHOBw7J5Q28fr4U,14105
184
- invar_tools-1.15.6.dist-info/METADATA,sha256=-aaDj9nhXLxzwN2p0mIcYojuY-gfj11YFvSepUs1Zkw,28595
185
- invar_tools-1.15.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
186
- invar_tools-1.15.6.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
187
- invar_tools-1.15.6.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
188
- invar_tools-1.15.6.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
189
- invar_tools-1.15.6.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
190
- invar_tools-1.15.6.dist-info/RECORD,,
194
+ invar_tools-1.17.0.dist-info/METADATA,sha256=6IIm7ef2K5XQM-9Enj46x_LyzcRC-jjO-fM2XpWDoLA,28595
195
+ invar_tools-1.17.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
196
+ invar_tools-1.17.0.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
197
+ invar_tools-1.17.0.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
198
+ invar_tools-1.17.0.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
199
+ invar_tools-1.17.0.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
200
+ invar_tools-1.17.0.dist-info/RECORD,,