invar-tools 1.15.5__py3-none-any.whl → 1.16.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.
@@ -0,0 +1,17 @@
1
+ # Embedded Node.js tools - runtime dependencies
2
+ # These are installed by scripts/embed_node_tools.py
3
+ # Run that script before building or testing
4
+
5
+ */node_modules/
6
+ */package.json
7
+
8
+ # eslint-plugin is unbundled (entire dist/ directory)
9
+ # All other tools are bundled (single cli.js file)
10
+ eslint-plugin/
11
+
12
+ # Common generated/temp files
13
+ *.log
14
+ .DS_Store
15
+ .npm/
16
+ npm-debug.log*
17
+ node_modules/.cache/
invar/node_tools/MANIFEST CHANGED
@@ -2,6 +2,7 @@
2
2
  # Auto-generated by scripts/embed_node_tools.py
3
3
  # Do not edit manually
4
4
 
5
+ eslint-plugin
5
6
  fc-runner
6
7
  quick-check
7
8
  ts-analyzer
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI for @invar/eslint-plugin
4
+ *
5
+ * Runs ESLint with @invar/* rules pre-configured.
6
+ * Outputs standard ESLint JSON format for integration with guard_ts.py.
7
+ *
8
+ * Usage:
9
+ * node cli.js [path] [--config=recommended|strict]
10
+ *
11
+ * Options:
12
+ * path Project directory to lint (default: current directory)
13
+ * --config Use 'recommended' or 'strict' preset (default: recommended)
14
+ * --help Show help message
15
+ */
16
+ import { ESLint } from 'eslint';
17
+ import { resolve, dirname } from 'path';
18
+ import { fileURLToPath } from 'url';
19
+ import plugin from './index.js';
20
+ // Get directory containing this CLI script (for resolving node_modules)
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = dirname(__filename);
23
+ function parseArgs(args) {
24
+ const projectPath = args.find(arg => !arg.startsWith('--')) || '.';
25
+ const configArg = args.find(arg => arg.startsWith('--config='));
26
+ const config = configArg?.split('=')[1] === 'strict' ? 'strict' : 'recommended';
27
+ const help = args.includes('--help') || args.includes('-h');
28
+ return { projectPath, config, help };
29
+ }
30
+ function printHelp() {
31
+ console.log(`
32
+ @invar/eslint-plugin - ESLint with Invar-specific rules
33
+
34
+ Usage:
35
+ node cli.js [path] [options]
36
+
37
+ Arguments:
38
+ path Project directory to lint (default: current directory)
39
+
40
+ Options:
41
+ --config=MODE Use 'recommended' or 'strict' preset (default: recommended)
42
+ --help, -h Show this help message
43
+
44
+ Examples:
45
+ node cli.js # Lint current directory (recommended mode)
46
+ node cli.js ./src # Lint specific directory
47
+ node cli.js --config=strict # Use strict mode (all rules as errors)
48
+
49
+ Output:
50
+ JSON format compatible with ESLint's --format=json
51
+ Exit code 0 if no errors, 1 if errors found
52
+ `);
53
+ }
54
+ async function main() {
55
+ const args = parseArgs(process.argv.slice(2));
56
+ if (args.help) {
57
+ printHelp();
58
+ process.exit(0);
59
+ }
60
+ const projectPath = resolve(args.projectPath);
61
+ // Validate resolved path is within current working directory or explicit allowed paths
62
+ // This prevents path traversal attacks via "../../../etc/passwd" patterns
63
+ const cwd = process.cwd();
64
+ if (!projectPath.startsWith(cwd) && !projectPath.startsWith('/')) {
65
+ console.error(`Error: Project path must be within current directory`);
66
+ console.error(` Requested: ${args.projectPath}`);
67
+ console.error(` Resolved: ${projectPath}`);
68
+ console.error(` Working dir: ${cwd}`);
69
+ process.exit(1);
70
+ }
71
+ try {
72
+ // Get the rules config for the selected mode
73
+ const selectedConfig = plugin.configs?.[args.config];
74
+ if (!selectedConfig || !selectedConfig.rules) {
75
+ console.error(`Config "${args.config}" not found or invalid`);
76
+ process.exit(1);
77
+ }
78
+ // Create ESLint instance with programmatic configuration
79
+ // Set cwd to CLI directory so ESLint can find parser in our node_modules
80
+ const eslint = new ESLint({
81
+ useEslintrc: false, // Don't load .eslintrc files
82
+ cwd: __dirname, // Set working directory to CLI location for module resolution
83
+ baseConfig: {
84
+ parser: '@typescript-eslint/parser',
85
+ parserOptions: {
86
+ ecmaVersion: 2022,
87
+ sourceType: 'module',
88
+ },
89
+ plugins: ['@invar'],
90
+ rules: selectedConfig.rules,
91
+ },
92
+ plugins: {
93
+ '@invar': plugin, // Register our plugin programmatically
94
+ },
95
+ }); // Type assertion for ESLint config complexity
96
+ // Lint the project
97
+ const results = await eslint.lintFiles([projectPath]);
98
+ // Output in standard ESLint JSON format (compatible with guard_ts.py)
99
+ const formatter = await eslint.loadFormatter('json');
100
+ const resultText = await Promise.resolve(formatter.format(results, {
101
+ cwd: projectPath,
102
+ rulesMeta: eslint.getRulesMetaForResults(results),
103
+ }));
104
+ console.log(resultText);
105
+ // Exit with error code if there are errors
106
+ const hasErrors = results.some(result => result.errorCount > 0);
107
+ process.exit(hasErrors ? 1 : 0);
108
+ }
109
+ catch (error) {
110
+ // Sanitize error message to avoid leaking file paths or system information
111
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
112
+ console.error(`ESLint failed: ${errorMessage}`);
113
+ process.exit(1);
114
+ }
115
+ }
116
+ main();
117
+ //# sourceMappingURL=cli.js.map
@@ -553,7 +553,8 @@ def init(
553
553
  skipped: list[str] = []
554
554
 
555
555
  # Add config file (.invar/config.toml or pyproject.toml)
556
- config_result = add_config(path, console)
556
+ # LX-05: Pass language for language-specific config generation
557
+ config_result = add_config(path, console, language)
557
558
  if isinstance(config_result, Failure):
558
559
  console.print(f"[red]Error:[/red] {config_result.failure()}")
559
560
  raise typer.Exit(1)
@@ -213,7 +213,10 @@ def _run_map_python(path: Path, top_n: int, json_output: bool) -> Result[None, s
213
213
  file_infos: list[FileInfo] = []
214
214
  sources: dict[str, str] = {}
215
215
 
216
- for py_file in discover_python_files(path):
216
+ # Convert generator to list to release directory handles immediately (DX-82)
217
+ python_files = list(discover_python_files(path))
218
+
219
+ for py_file in python_files:
217
220
  try:
218
221
  content = py_file.read_text(encoding="utf-8")
219
222
  rel_path = str(py_file.relative_to(path))
@@ -228,6 +231,9 @@ def _run_map_python(path: Path, top_n: int, json_output: bool) -> Result[None, s
228
231
  console.print(f"[yellow]Warning:[/yellow] {py_file}: {e}")
229
232
  continue
230
233
 
234
+ # Release file list to free memory (DX-82)
235
+ del python_files
236
+
231
237
  if not file_infos:
232
238
  return Failure(
233
239
  "No Python symbols found.\n\n"
@@ -664,7 +664,9 @@ def _parse_tsc_line(line: str) -> TypeScriptViolation | None:
664
664
 
665
665
  # @shell_complexity: CLI tool integration with JSON parsing and error handling
666
666
  def run_eslint(project_path: Path) -> Result[list[TypeScriptViolation], str]:
667
- """Run ESLint for code quality checks.
667
+ """Run ESLint with @invar/eslint-plugin rules.
668
+
669
+ Uses @invar/eslint-plugin CLI which pre-loads Invar-specific rules.
668
670
 
669
671
  Args:
670
672
  project_path: Path to project root.
@@ -677,9 +679,12 @@ def run_eslint(project_path: Path) -> Result[list[TypeScriptViolation], str]:
677
679
  return Failure(f"Project path does not exist: {project_path}")
678
680
 
679
681
  try:
682
+ # Get command for @invar/eslint-plugin (embedded or local dev)
683
+ cmd = _get_invar_package_cmd("eslint-plugin", project_path)
684
+ cmd.append(str(project_path)) # Add project path as argument
685
+
680
686
  result = subprocess.run(
681
- ["npx", "eslint", ".", "--format", "json", "--ext", ".ts,.tsx"],
682
- cwd=project_path,
687
+ cmd,
683
688
  capture_output=True,
684
689
  text=True,
685
690
  timeout=120,
@@ -748,6 +753,23 @@ def run_vitest(project_path: Path) -> Result[list[TypeScriptViolation], str]:
748
753
  except json.JSONDecodeError:
749
754
  pass
750
755
 
756
+ # LX-15 Phase 1: Generate doctests before running vitest
757
+ try:
758
+ doctest_result = subprocess.run(
759
+ ["node", "scripts/generate-doctests.mjs", "doctest.config.json"],
760
+ cwd=project_path,
761
+ capture_output=True,
762
+ text=True,
763
+ timeout=60,
764
+ )
765
+ # Doctest generation failure is not fatal - continue with regular tests
766
+ if doctest_result.returncode != 0:
767
+ # Log warning but don't fail
768
+ pass
769
+ except (FileNotFoundError, subprocess.TimeoutExpired):
770
+ # Doctest generation not available or timed out - continue with regular tests
771
+ pass
772
+
751
773
  try:
752
774
  result = subprocess.run(
753
775
  ["npx", "vitest", "run", "--reporter=json"],
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.5
3
+ Version: 1.16.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
@@ -52,9 +52,11 @@ invar/mcp/__init__.py,sha256=n3S7QwMjSMqOMT8cI2jf9E0yZPjKmBOJyIYhq4WZ8TQ,226
52
52
  invar/mcp/__main__.py,sha256=ZcIT2U6xUyGOWucl4jq422BDE3lRLjqyxb9pFylRBdk,219
53
53
  invar/mcp/handlers.py,sha256=b0LRWKMpu6lnjPV7SozH0cxKYJufVWy3eJhiAu3of-M,15947
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
59
+ invar/node_tools/eslint-plugin/cli.js,sha256=TyBGCIZ0LiGIEjESRCh1VkY5ooLmrRbFY9dd5Vfzw9c,4647
58
60
  invar/node_tools/fc-runner/cli.js,sha256=72_gIhvnx2peKITdzdnFWI5fzGaNTS3BcEqyS628cI0,243277
59
61
  invar/node_tools/quick-check/cli.js,sha256=dwV3hdJleFQga2cKUn3PPfQDvvujhzKdjQcIvWsKgM0,66196
60
62
  invar/node_tools/ts-analyzer/cli.js,sha256=SvZ6HyjmobpP8NAZqXFiy8BwH_t5Hb17Ytar_18udaQ,4092887
@@ -78,7 +80,7 @@ invar/shell/py_refs.py,sha256=Vjz50lmt9prDBcBv4nkkODdiJ7_DKu5zO4UPZBjAfmM,4638
78
80
  invar/shell/skill_manager.py,sha256=Mr7Mh9rxPSKSAOTJCAM5ZHiG5nfUf6KQVCuD4LBNHSI,12440
79
81
  invar/shell/subprocess_env.py,sha256=9oXl3eMEbzLsFEgMHqobEw6oW_wV0qMEP7pklwm58Pw,11453
80
82
  invar/shell/template_engine.py,sha256=eNKMz7R8g9Xp3_1TGx-QH137jf52E0u3KaVcnotu1Tg,12056
81
- invar/shell/templates.py,sha256=31f5ieoGeWU0qqfLJUMWnz0yyLa1FBc_sOz6UGzToqk,13884
83
+ invar/shell/templates.py,sha256=ilhGysbUcdkUFqPgv6ySVmKI3imS_cwYNCWTCdyb5cY,15407
82
84
  invar/shell/testing.py,sha256=rTNBH0Okh2qtG9ohSXOz487baQ2gXrWT3s_WECW3HJs,11143
83
85
  invar/shell/ts_compiler.py,sha256=nA8brnOhThj9J_J3vAEGjDsM4NjbWQ_eX8Yf4pHPOgk,6672
84
86
  invar/shell/commands/__init__.py,sha256=MEkKwVyjI9DmkvBpJcuumXo2Pg_FFkfEr-Rr3nrAt7A,284
@@ -86,10 +88,10 @@ invar/shell/commands/doc.py,sha256=SOLDoCXXGxx_JU0PKXlAIGEF36PzconHmmAtL-rM6D4,1
86
88
  invar/shell/commands/feedback.py,sha256=lLxEeWW_71US_vlmorFrGXS8IARB9nbV6D0zruLs660,7640
87
89
  invar/shell/commands/guard.py,sha256=xTQ8cPp-x1xMCtufKxmMNUSpIpH31uUjziAB8ifCnC0,24837
88
90
  invar/shell/commands/hooks.py,sha256=W-SOnT4VQyUvXwipozkJwgEYfiOJGz7wksrbcdWegUg,2356
89
- invar/shell/commands/init.py,sha256=vaPo0p7xBm3Nfgu9ytcvAjgk4dQBKvyEhrz_Cg1URMQ,23557
91
+ invar/shell/commands/init.py,sha256=rtoPFsfq7xRZ6lfTipWT1OejNK5wfzqu1ncXi1kizU0,23634
90
92
  invar/shell/commands/merge.py,sha256=nuvKo8m32-OL-SCQlS4SLKmOZxQ3qj-1nGCx1Pgzifw,8183
91
93
  invar/shell/commands/mutate.py,sha256=GwemiO6LlbGCBEQsBFnzZuKhF-wIMEl79GAMnKUWc8U,5765
92
- invar/shell/commands/perception.py,sha256=HewSv6Kv8Gw2UQqkGY2rP5YKlnwyC3LBrQ2hFVXXw30,19304
94
+ invar/shell/commands/perception.py,sha256=Vl6zgxkqtS3QRXBat6U_utNhpViyPFoPh899-OtDLgQ,19493
93
95
  invar/shell/commands/skill.py,sha256=oKVyaxQ_LK28FpJhRpBDpXcpRdUBK3n6rC0qD77ax1M,5803
94
96
  invar/shell/commands/sync_self.py,sha256=nmqBry7V2_enKwy2zzHg8UoedZNicLe3yKDhjmBeZ68,3880
95
97
  invar/shell/commands/template_sync.py,sha256=aNWyFPMFT7pSwHrvwGCqcKAwb4dp7S9tvZzy9H4gAnw,16094
@@ -100,7 +102,7 @@ invar/shell/prove/__init__.py,sha256=ZqlbmyMFJf6yAle8634jFuPRv8wNvHps8loMlOJyf8A
100
102
  invar/shell/prove/accept.py,sha256=cnY_6jzU1EBnpLF8-zWUWcXiSXtCwxPsXEYXsSVPG38,3717
101
103
  invar/shell/prove/cache.py,sha256=jbNdrvfLjvK7S0iqugErqeabb4YIbQuwIlcSRyCKbcg,4105
102
104
  invar/shell/prove/crosshair.py,sha256=XhJDsQWIriX9SuqeflUYvJgp9gJTDH7I7Uka6zjNzZ0,16734
103
- invar/shell/prove/guard_ts.py,sha256=M285vnaKGC3Dokyu7PrjlNk-sT1TP8H821fz5K4CL8c,31717
105
+ invar/shell/prove/guard_ts.py,sha256=i0F_56_aIgtqzJF2pfRAYJ2Tyjsuv-xhT8N_ZsBfis8,32567
104
106
  invar/shell/prove/hypothesis.py,sha256=QUclOOUg_VB6wbmHw8O2EPiL5qBOeBRqQeM04AVuLw0,9880
105
107
  invar/templates/CLAUDE.md.template,sha256=eaGU3SyRO_NEifw5b26k3srgQH4jyeujjCJ-HbM36_w,4913
106
108
  invar/templates/__init__.py,sha256=cb3ht8KPK5oBn5oG6HsTznujmo9WriJ_P--fVxJwycc,45
@@ -181,10 +183,10 @@ invar/templates/skills/invar-reflect/template.md,sha256=Rr5hvbllvmd8jSLf_0ZjyKt6
181
183
  invar/templates/skills/investigate/SKILL.md.jinja,sha256=cp6TBEixBYh1rLeeHOR1yqEnFqv1NZYePORMnavLkQI,3231
182
184
  invar/templates/skills/propose/SKILL.md.jinja,sha256=6BuKiCqO1AEu3VtzMHy1QWGqr_xqG9eJlhbsKT4jev4,3463
183
185
  invar/templates/skills/review/SKILL.md.jinja,sha256=ET5mbdSe_eKgJbi2LbgFC-z1aviKcHOBw7J5Q28fr4U,14105
184
- invar_tools-1.15.5.dist-info/METADATA,sha256=zU4u2AF-x_H2DTuFNXka2IdQUQ20SVTdyhKv28dI2tA,28595
185
- invar_tools-1.15.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
186
- invar_tools-1.15.5.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
187
- invar_tools-1.15.5.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
188
- invar_tools-1.15.5.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
189
- invar_tools-1.15.5.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
190
- invar_tools-1.15.5.dist-info/RECORD,,
186
+ invar_tools-1.16.0.dist-info/METADATA,sha256=JUYnszfz8YwiI3iRSXAlSnpVnCtBOtHGT5eeUb7w68w,28595
187
+ invar_tools-1.16.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
188
+ invar_tools-1.16.0.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
189
+ invar_tools-1.16.0.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
190
+ invar_tools-1.16.0.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
191
+ invar_tools-1.16.0.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
192
+ invar_tools-1.16.0.dist-info/RECORD,,