invar-tools 1.17.2__py3-none-any.whl → 1.17.5__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 (63) hide show
  1. invar/mcp/server.py +9 -5
  2. invar/node_tools/.gitignore +10 -6
  3. invar/node_tools/eslint-plugin/rules/__tests__/behavior.test.js +1321 -0
  4. invar/node_tools/eslint-plugin/rules/__tests__/behavior.test.js.map +1 -0
  5. invar/node_tools/eslint-plugin/rules/__tests__/e2e-scenarios.test.js +414 -0
  6. invar/node_tools/eslint-plugin/rules/__tests__/e2e-scenarios.test.js.map +1 -0
  7. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/function-lengths.js +142 -0
  8. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/function-lengths.js.map +1 -0
  9. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/has-io-imports.js +15 -0
  10. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/has-io-imports.js.map +1 -0
  11. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/valid-small.js +27 -0
  12. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/valid-small.js.map +1 -0
  13. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/exported-functions.js +43 -0
  14. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/exported-functions.js.map +1 -0
  15. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/shell/with-io.js +27 -0
  16. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/shell/with-io.js.map +1 -0
  17. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/tests/large.test.js +260 -0
  18. invar/node_tools/eslint-plugin/rules/__tests__/fixtures/tests/large.test.js.map +1 -0
  19. invar/node_tools/eslint-plugin/rules/max-file-lines.js +105 -0
  20. invar/node_tools/eslint-plugin/rules/max-file-lines.js.map +1 -0
  21. invar/node_tools/eslint-plugin/rules/max-function-lines.js +133 -0
  22. invar/node_tools/eslint-plugin/rules/max-function-lines.js.map +1 -0
  23. invar/node_tools/eslint-plugin/rules/no-any-in-schema.js +39 -0
  24. invar/node_tools/eslint-plugin/rules/no-any-in-schema.js.map +1 -0
  25. invar/node_tools/eslint-plugin/rules/no-empty-schema.js +69 -0
  26. invar/node_tools/eslint-plugin/rules/no-empty-schema.js.map +1 -0
  27. invar/node_tools/eslint-plugin/rules/no-impure-calls-in-core.js +52 -0
  28. invar/node_tools/eslint-plugin/rules/no-impure-calls-in-core.js.map +1 -0
  29. invar/node_tools/eslint-plugin/rules/no-io-in-core.js +99 -0
  30. invar/node_tools/eslint-plugin/rules/no-io-in-core.js.map +1 -0
  31. invar/node_tools/eslint-plugin/rules/no-pure-logic-in-shell.js +197 -0
  32. invar/node_tools/eslint-plugin/rules/no-pure-logic-in-shell.js.map +1 -0
  33. invar/node_tools/eslint-plugin/rules/no-redundant-type-schema.js +99 -0
  34. invar/node_tools/eslint-plugin/rules/no-redundant-type-schema.js.map +1 -0
  35. invar/node_tools/eslint-plugin/rules/no-runtime-imports.js +66 -0
  36. invar/node_tools/eslint-plugin/rules/no-runtime-imports.js.map +1 -0
  37. invar/node_tools/eslint-plugin/rules/require-complete-validation.js +104 -0
  38. invar/node_tools/eslint-plugin/rules/require-complete-validation.js.map +1 -0
  39. invar/node_tools/eslint-plugin/rules/require-jsdoc-example.js +81 -0
  40. invar/node_tools/eslint-plugin/rules/require-jsdoc-example.js.map +1 -0
  41. invar/node_tools/eslint-plugin/rules/require-schema-validation.js +308 -0
  42. invar/node_tools/eslint-plugin/rules/require-schema-validation.js.map +1 -0
  43. invar/node_tools/eslint-plugin/rules/shell-complexity.js +273 -0
  44. invar/node_tools/eslint-plugin/rules/shell-complexity.js.map +1 -0
  45. invar/node_tools/eslint-plugin/rules/shell-result-type.js +138 -0
  46. invar/node_tools/eslint-plugin/rules/shell-result-type.js.map +1 -0
  47. invar/node_tools/eslint-plugin/rules/thin-entry-points.js +174 -0
  48. invar/node_tools/eslint-plugin/rules/thin-entry-points.js.map +1 -0
  49. invar/node_tools/eslint-plugin/utils/layer-detection.js +91 -0
  50. invar/node_tools/eslint-plugin/utils/layer-detection.js.map +1 -0
  51. invar/node_tools/eslint-plugin/utils/math-example.js +31 -0
  52. invar/node_tools/eslint-plugin/utils/math-example.js.map +1 -0
  53. invar/shell/commands/guard.py +1 -1
  54. invar/shell/commands/perception.py +40 -25
  55. invar/shell/commands/test.py +2 -2
  56. invar/shell/prove/guard_ts.py +47 -22
  57. {invar_tools-1.17.2.dist-info → invar_tools-1.17.5.dist-info}/METADATA +1 -1
  58. {invar_tools-1.17.2.dist-info → invar_tools-1.17.5.dist-info}/RECORD +63 -13
  59. {invar_tools-1.17.2.dist-info → invar_tools-1.17.5.dist-info}/WHEEL +0 -0
  60. {invar_tools-1.17.2.dist-info → invar_tools-1.17.5.dist-info}/entry_points.txt +0 -0
  61. {invar_tools-1.17.2.dist-info → invar_tools-1.17.5.dist-info}/licenses/LICENSE +0 -0
  62. {invar_tools-1.17.2.dist-info → invar_tools-1.17.5.dist-info}/licenses/LICENSE-GPL +0 -0
  63. {invar_tools-1.17.2.dist-info → invar_tools-1.17.5.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Layer Detection Utilities
3
+ *
4
+ * Detects which architectural layer a file belongs to:
5
+ * - Core: Pure logic, strict limits
6
+ * - Shell: I/O operations, relaxed limits
7
+ * - Tests: Test files, most relaxed limits
8
+ * - Default: Other files
9
+ */
10
+ /**
11
+ * Default limits for each layer (LX-10).
12
+ *
13
+ * TypeScript limits = Python limits × 1.3 (due to type overhead).
14
+ * - Python Core: 500/50 → TypeScript Core: 650/65
15
+ * - Python Shell: 700/100 → TypeScript Shell: 910/130
16
+ * - Python Tests: 1000/200 → TypeScript Tests: 1300/260
17
+ * - Python Default: 600/80 → TypeScript Default: 780/104
18
+ */
19
+ export const LAYER_LIMITS = {
20
+ core: {
21
+ maxFileLines: 650,
22
+ maxFunctionLines: 65,
23
+ },
24
+ shell: {
25
+ maxFileLines: 910,
26
+ maxFunctionLines: 130,
27
+ },
28
+ tests: {
29
+ maxFileLines: 1300,
30
+ maxFunctionLines: 260,
31
+ },
32
+ default: {
33
+ maxFileLines: 780,
34
+ maxFunctionLines: 104,
35
+ },
36
+ };
37
+ /**
38
+ * Detect layer from filename.
39
+ *
40
+ * Priority: tests > core > shell > default
41
+ *
42
+ * @example
43
+ * getLayer('/project/src/core/parser.ts') // => 'core'
44
+ * getLayer('/project/tests/parser.test.ts') // => 'tests'
45
+ * getLayer('/project/src/shell/io.ts') // => 'shell'
46
+ */
47
+ export function getLayer(filename) {
48
+ const normalized = filename.replace(/\\/g, '/').toLowerCase();
49
+ // Priority 1: Test files
50
+ if (normalized.includes('/test/') ||
51
+ normalized.includes('/tests/') ||
52
+ normalized.includes('/__tests__/') ||
53
+ normalized.endsWith('.test.ts') ||
54
+ normalized.endsWith('.test.tsx') ||
55
+ normalized.endsWith('.test.js') ||
56
+ normalized.endsWith('.test.jsx') ||
57
+ normalized.endsWith('.spec.ts') ||
58
+ normalized.endsWith('.spec.tsx') ||
59
+ normalized.endsWith('.spec.js') ||
60
+ normalized.endsWith('.spec.jsx')) {
61
+ return 'tests';
62
+ }
63
+ // Priority 2: Core layer
64
+ // Use path segment matching to avoid false positives like '/hardcore/'
65
+ if (normalized.includes('/core/') ||
66
+ normalized.endsWith('/core') ||
67
+ normalized.startsWith('core/')) {
68
+ return 'core';
69
+ }
70
+ // Priority 3: Shell layer
71
+ // Use path segment matching to avoid false positives like '/eggshell/'
72
+ if (normalized.includes('/shell/') ||
73
+ normalized.endsWith('/shell') ||
74
+ normalized.startsWith('shell/')) {
75
+ return 'shell';
76
+ }
77
+ // Default layer
78
+ return 'default';
79
+ }
80
+ /**
81
+ * Get limits for a filename.
82
+ *
83
+ * @example
84
+ * getLimits('/project/src/core/parser.ts')
85
+ * // => { maxFileLines: 650, maxFunctionLines: 65 }
86
+ */
87
+ export function getLimits(filename) {
88
+ const layer = getLayer(filename);
89
+ return LAYER_LIMITS[layer];
90
+ }
91
+ //# sourceMappingURL=layer-detection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layer-detection.js","sourceRoot":"","sources":["../../src/utils/layer-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GAA+B;IACtD,IAAI,EAAE;QACJ,YAAY,EAAE,GAAG;QACjB,gBAAgB,EAAE,EAAE;KACrB;IACD,KAAK,EAAE;QACL,YAAY,EAAE,GAAG;QACjB,gBAAgB,EAAE,GAAG;KACtB;IACD,KAAK,EAAE;QACL,YAAY,EAAE,IAAI;QAClB,gBAAgB,EAAE,GAAG;KACtB;IACD,OAAO,EAAE;QACP,YAAY,EAAE,GAAG;QACjB,gBAAgB,EAAE,GAAG;KACtB;CACF,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CAAC,QAAgB;IACvC,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAE9D,yBAAyB;IACzB,IACE,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC7B,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC9B,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC;QAClC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC/B,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC;QAChC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC/B,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC;QAChC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC/B,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC;QAChC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC/B,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,EAChC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,yBAAyB;IACzB,uEAAuE;IACvE,IACE,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC7B,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC;QAC5B,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,EAC9B,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,0BAA0B;IAC1B,uEAAuE;IACvE,IACE,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC9B,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC7B,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,EAC/B,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,gBAAgB;IAChB,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACjC,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Multiply two numbers together.
3
+ *
4
+ * @example
5
+ * multiply(2, 3) // => 6
6
+ *
7
+ * @example
8
+ * multiply(0, 100) // => 0
9
+ *
10
+ * @example
11
+ * multiply(-2, 5) // => -10
12
+ */
13
+ export function multiply(a, b) {
14
+ return a * b;
15
+ }
16
+ /**
17
+ * Add two numbers together.
18
+ *
19
+ * @example
20
+ * add(2, 3) // => 5
21
+ *
22
+ * @example
23
+ * add(-1, 1) // => 0
24
+ *
25
+ * @example
26
+ * add(2, 2) // => 4
27
+ */
28
+ export function add(a, b) {
29
+ return a + b;
30
+ }
31
+ //# sourceMappingURL=math-example.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"math-example.js","sourceRoot":"","sources":["../../src/utils/math-example.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,QAAQ,CAAC,CAAS,EAAE,CAAS;IAC3C,OAAO,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,CAAS;IACtC,OAAO,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
@@ -274,7 +274,7 @@ def guard(
274
274
  changed_result = handle_changed_mode(path)
275
275
  if isinstance(changed_result, Failure):
276
276
  if changed_result.failure() == "NO_CHANGES":
277
- console.print("[green]No changed Python files.[/green]")
277
+ console.print("[green]No changed files to verify.[/green]")
278
278
  raise typer.Exit(0)
279
279
  console.print(f"[red]Error:[/red] {changed_result.failure()}")
280
280
  raise typer.Exit(1)
@@ -31,10 +31,21 @@ if TYPE_CHECKING:
31
31
  console = Console()
32
32
 
33
33
 
34
+ def _has_typescript_files(path: Path) -> bool:
35
+ """Check if directory contains TypeScript files (.ts, .tsx).
36
+
37
+ DX-78b: Fallback language detection when path is subdirectory
38
+ without tsconfig/package.json markers in parent directories.
39
+ """
40
+ from invar.shell.fs import discover_typescript_files
41
+
42
+ return bool(list(discover_typescript_files(path)))
43
+
44
+
34
45
  # @shell_complexity: Symbol map generation with sorting and output modes
35
46
  def run_map(path: Path, top_n: int, json_output: bool) -> Result[None, str]:
36
47
  """
37
- Run the map command.
48
+ Run map command.
38
49
 
39
50
  Scans project and generates perception map with reference counts.
40
51
  LX-06: Supports TypeScript projects (basic symbol listing).
@@ -46,6 +57,14 @@ def run_map(path: Path, top_n: int, json_output: bool) -> Result[None, str]:
46
57
  from invar.shell.commands.init import detect_language
47
58
 
48
59
  project_language = detect_language(path)
60
+
61
+ # DX-78b: Fallback language detection by checking file extensions
62
+ # When path is a subdirectory without tsconfig/package.json markers,
63
+ # try detecting from actual file contents
64
+ if project_language == "python":
65
+ if _has_typescript_files(path):
66
+ project_language = "typescript"
67
+
49
68
  if project_language == "typescript":
50
69
  return _run_map_typescript(path, top_n, json_output)
51
70
 
@@ -174,7 +193,9 @@ def _run_sig_typescript(
174
193
  console.print(f" @post {post}")
175
194
  if s.members:
176
195
  for m in s.members:
177
- console.print(f" [{m['kind']}] {m['name']}: {m.get('signature', '')}")
196
+ console.print(
197
+ f" [{m['kind']}] {m['name']}: {m.get('signature', '')}"
198
+ )
178
199
  console.print()
179
200
 
180
201
  return Success(None)
@@ -236,12 +257,13 @@ def _run_map_python(path: Path, top_n: int, json_output: bool) -> Result[None, s
236
257
 
237
258
  if not file_infos:
238
259
  return Failure(
239
- "No Python symbols found.\n\n"
240
- "Available tools:\n"
241
- "- invar sig <file.py> — Extract signatures\n"
242
- "- invar refs <file.py>::Symbol — Find references\n"
260
+ "No source files found in this directory.\n\n"
261
+ "💡 Available tools:\n"
262
+ "- invar sig <file> — Extract signatures\n"
263
+ "- invar refs <file>::Symbol — Find references\n"
243
264
  "- invar_doc_* — Document navigation\n"
244
- "- invar_guard — Static verification"
265
+ "- invar_guard — Static verification\n\n"
266
+ "Supported languages: Python, TypeScript"
245
267
  )
246
268
 
247
269
  # Build perception map
@@ -274,12 +296,13 @@ def _run_map_typescript(path: Path, top_n: int, json_output: bool) -> Result[Non
274
296
 
275
297
  if not data.get("symbols"):
276
298
  return Failure(
277
- "No TypeScript symbols found.\n\n"
278
- "Available tools:\n"
279
- "- invar sig <file.ts> — Extract signatures\n"
280
- "- invar refs <file.ts>::Symbol — Find references\n"
299
+ "No source files found in this directory.\n\n"
300
+ "💡 Available tools:\n"
301
+ "- invar sig <file> — Extract signatures\n"
302
+ "- invar refs <file>::Symbol — Find references\n"
281
303
  "- invar_doc_* — Document navigation\n"
282
- "- invar_guard — Static verification"
304
+ "- invar_guard — Static verification\n\n"
305
+ "Supported languages: Python, TypeScript"
283
306
  )
284
307
 
285
308
  # Output using TS Compiler API format
@@ -394,16 +417,11 @@ def run_refs(target: str, json_output: bool) -> Result[None, str]:
394
417
  elif suffix in (".py", ".pyi"):
395
418
  return _run_refs_python(file_path, symbol_name, json_output)
396
419
  else:
397
- return Failure(
398
- f"Unsupported file type: {suffix}\n\n"
399
- "Supported: .py, .pyi, .ts, .tsx"
400
- )
420
+ return Failure(f"Unsupported file type: {suffix}\n\nSupported: .py, .pyi, .ts, .tsx")
401
421
 
402
422
 
403
423
  # @shell_complexity: Reference finding with output formatting and error handling
404
- def _run_refs_python(
405
- file_path: Path, symbol_name: str, json_output: bool
406
- ) -> Result[None, str]:
424
+ def _run_refs_python(file_path: Path, symbol_name: str, json_output: bool) -> Result[None, str]:
407
425
  """Find references in Python using jedi."""
408
426
  from invar.shell.py_refs import find_all_references_to_symbol
409
427
 
@@ -460,15 +478,14 @@ def _run_refs_python(
460
478
  @dataclass
461
479
  class _SymbolPosition:
462
480
  """Temporary holder for symbol position during refs lookup."""
481
+
463
482
  line: int
464
483
  column: int
465
484
  name: str
466
485
 
467
486
 
468
487
  # @shell_complexity: TypeScript refs with symbol lookup and output formatting
469
- def _run_refs_typescript(
470
- file_path: Path, symbol_name: str, json_output: bool
471
- ) -> Result[None, str]:
488
+ def _run_refs_typescript(file_path: Path, symbol_name: str, json_output: bool) -> Result[None, str]:
472
489
  """Find references in TypeScript using TS Compiler API."""
473
490
  from invar.shell.ts_compiler import is_typescript_available, run_refs_typescript
474
491
 
@@ -499,9 +516,7 @@ def _run_refs_typescript(
499
516
  # Extract column if available, default to 0
500
517
  column = member.get("column", 0)
501
518
  symbol = _SymbolPosition(
502
- line=member["line"],
503
- column=column,
504
- name=symbol_name
519
+ line=member["line"], column=column, name=symbol_name
505
520
  )
506
521
  break
507
522
  if symbol:
@@ -53,7 +53,7 @@ def test(
53
53
  raise typer.Exit(1)
54
54
  files = list(changed_result.unwrap())
55
55
  if not files:
56
- console.print("[green]No changed Python files.[/green]")
56
+ console.print("[green]No changed files to test.[/green]")
57
57
  raise typer.Exit(0)
58
58
  elif target:
59
59
  files = [Path(target)]
@@ -99,7 +99,7 @@ def verify(
99
99
  raise typer.Exit(1)
100
100
  files = list(changed_result.unwrap())
101
101
  if not files:
102
- console.print("[green]No changed Python files.[/green]")
102
+ console.print("[green]No changed files to test.[/green]")
103
103
  raise typer.Exit(0)
104
104
  elif target:
105
105
  files = [Path(target)]
@@ -13,6 +13,7 @@ import contextlib
13
13
  import json
14
14
  import re
15
15
  import subprocess
16
+ import tempfile
16
17
  from dataclasses import dataclass, field
17
18
  from pathlib import Path
18
19
  from typing import Literal
@@ -104,9 +105,7 @@ def check_ts_complexity_debt(
104
105
  """
105
106
  # Count unaddressed shell-complexity warnings
106
107
  unaddressed = [
107
- v
108
- for v in violations
109
- if v.rule == "@invar/shell-complexity" and v.severity == "warning"
108
+ v for v in violations if v.rule == "@invar/shell-complexity" and v.severity == "warning"
110
109
  ]
111
110
 
112
111
  if len(unaddressed) >= limit:
@@ -142,9 +141,15 @@ def format_typescript_guard_v2(result: TypeScriptGuardResult) -> dict:
142
141
  """
143
142
  # Count violations by source
144
143
  tsc_errors = sum(1 for v in result.violations if v.source == "tsc" and v.severity == "error")
145
- tsc_warnings = sum(1 for v in result.violations if v.source == "tsc" and v.severity == "warning")
146
- eslint_errors = sum(1 for v in result.violations if v.source == "eslint" and v.severity == "error")
147
- eslint_warnings = sum(1 for v in result.violations if v.source == "eslint" and v.severity == "warning")
144
+ tsc_warnings = sum(
145
+ 1 for v in result.violations if v.source == "tsc" and v.severity == "warning"
146
+ )
147
+ eslint_errors = sum(
148
+ 1 for v in result.violations if v.source == "eslint" and v.severity == "error"
149
+ )
150
+ eslint_warnings = sum(
151
+ 1 for v in result.violations if v.source == "eslint" and v.severity == "warning"
152
+ )
148
153
  vitest_failures = sum(1 for v in result.violations if v.source == "vitest")
149
154
 
150
155
  # Count files checked (unique files in violations + estimate from available tools)
@@ -459,14 +464,16 @@ def run_ts_analyzer(project_path: Path) -> Result[dict, str]:
459
464
  )
460
465
  # Extract key metrics from text output
461
466
  output = summary_result.stdout
462
- coverage_match = re.search(r'Contract coverage: (\d+)%', output)
467
+ coverage_match = re.search(r"Contract coverage: (\d+)%", output)
463
468
  coverage = int(coverage_match.group(1)) if coverage_match else None
464
469
 
465
- return Success({
466
- "coverage": coverage,
467
- "summary_mode": True,
468
- "note": "Full JSON output too large, using summary metrics"
469
- })
470
+ return Success(
471
+ {
472
+ "coverage": coverage,
473
+ "summary_mode": True,
474
+ "note": "Full JSON output too large, using summary metrics",
475
+ }
476
+ )
470
477
  except Exception:
471
478
  return Failure(f"JSON parse error: {str(e)[:100]}")
472
479
 
@@ -481,9 +488,7 @@ def run_ts_analyzer(project_path: Path) -> Result[dict, str]:
481
488
 
482
489
 
483
490
  # @shell_complexity: Error handling branches for subprocess/JSON parsing
484
- def run_fc_runner(
485
- project_path: Path, *, seed: int = 42, num_runs: int = 100
486
- ) -> Result[dict, str]:
491
+ def run_fc_runner(project_path: Path, *, seed: int = 42, num_runs: int = 100) -> Result[dict, str]:
487
492
  """Run @invar/fc-runner for property-based testing.
488
493
 
489
494
  Calls the Node component via npx with JSON output mode.
@@ -747,17 +752,35 @@ def run_eslint(project_path: Path) -> Result[list[TypeScriptViolation], str]:
747
752
  cmd = _get_invar_package_cmd("eslint-plugin", project_path)
748
753
  cmd.append(str(project_path)) # Add project path as argument
749
754
 
750
- result = subprocess.run(
751
- cmd,
752
- capture_output=True,
753
- text=True,
754
- timeout=120,
755
- )
755
+ # Use temp file to avoid subprocess 64KB buffer limit
756
+ # ESLint output can be large for big projects
757
+ with tempfile.NamedTemporaryFile(mode="w+", suffix=".json", delete=False) as temp_file:
758
+ temp_path = temp_file.name
759
+
760
+ try:
761
+ # Redirect stdout to temp file
762
+ with Path(temp_path).open("w") as f:
763
+ result = subprocess.run(
764
+ cmd,
765
+ stdout=f,
766
+ stderr=subprocess.PIPE,
767
+ timeout=120,
768
+ cwd=project_path,
769
+ text=True,
770
+ )
771
+
772
+ # Read from temp file
773
+ with Path(temp_path).open("r") as f:
774
+ eslint_json = f.read()
775
+ finally:
776
+ # Clean up temp file
777
+ with contextlib.suppress(OSError):
778
+ Path(temp_path).unlink()
756
779
 
757
780
  violations: list[TypeScriptViolation] = []
758
781
 
759
782
  try:
760
- eslint_output = json.loads(result.stdout)
783
+ eslint_output = json.loads(eslint_json)
761
784
  for file_result in eslint_output:
762
785
  file_path = file_result.get("filePath", "")
763
786
  # Make path relative
@@ -781,6 +804,8 @@ def run_eslint(project_path: Path) -> Result[list[TypeScriptViolation], str]:
781
804
  # ESLint may output non-JSON on certain errors
782
805
  if result.returncode != 0 and result.stderr:
783
806
  return Failure(f"ESLint error: {result.stderr[:200]}")
807
+ return Failure("ESLint output parsing failed: JSON decode error")
808
+ return Failure("ESLint output parsing failed: JSON decode error")
784
809
 
785
810
  return Success(violations)
786
811
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invar-tools
3
- Version: 1.17.2
3
+ Version: 1.17.5
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
@@ -51,8 +51,8 @@ invar/core/patterns/types.py,sha256=ULAlWuAdmO6CFcEDjTrWBfzNTBsnomAl2d25tR11ihU,
51
51
  invar/mcp/__init__.py,sha256=n3S7QwMjSMqOMT8cI2jf9E0yZPjKmBOJyIYhq4WZ8TQ,226
52
52
  invar/mcp/__main__.py,sha256=ZcIT2U6xUyGOWucl4jq422BDE3lRLjqyxb9pFylRBdk,219
53
53
  invar/mcp/handlers.py,sha256=Kls1aXnYmGcMvib_Mesfz5FjBaL7lmrhKaw_P2JDnyw,16551
54
- invar/mcp/server.py,sha256=WmVNs2_AcOMbmp3tEgWR57hjqqJkMx3nV3z9fcNuSAA,20109
55
- invar/node_tools/.gitignore,sha256=Tu_FtBhQTKPl9LHokxUTCTcxPadynDy6VPo0Z1WCBAQ,388
54
+ invar/mcp/server.py,sha256=zSpY9bCFuq4mWe7XfolTnwHffhdmoyN40aFL4L7dFrE,20407
55
+ invar/node_tools/.gitignore,sha256=M2kz8Iw7Kzmi44mKo1r7_HOZMh79a7dFDdRrqXyaEhI,530
56
56
  invar/node_tools/MANIFEST,sha256=2Z2at-27MK8K7DSjOjjtR4faTbt6eCiKQuEfvP_lwH8,145
57
57
  invar/node_tools/__init__.py,sha256=HzILh3jtP28Lm2jZwss1SY65ECxbtw2J2uFpXQA6Y94,1740
58
58
  invar/node_tools/ts-query.js,sha256=fEc_f0JT_Mb18dEoA4_vJoazvd7Lqv_rsed4eHSAbCg,13303
@@ -2594,6 +2594,56 @@ invar/node_tools/eslint-plugin/node_modules/yocto-queue/index.js,sha256=A0I9LFq6
2594
2594
  invar/node_tools/eslint-plugin/node_modules/yocto-queue/license,sha256=XJMtiCVrSrlY9kqFb6SOi9H1W8HZa4FJxlaJ4MYXidM,1117
2595
2595
  invar/node_tools/eslint-plugin/node_modules/yocto-queue/package.json,sha256=6U1XHQPGXJTqsiFvT953ORihUtXTblZy4fXBWP9qxC0,725
2596
2596
  invar/node_tools/eslint-plugin/node_modules/yocto-queue/readme.md,sha256=rfWnHVL8T6u1HAmqZ9WPj2G6oTjG0HBbGoBZWPDFhKU,2096
2597
+ invar/node_tools/eslint-plugin/rules/max-file-lines.js,sha256=UpvTYmI_e0SedKCyzAkx1ZbRSiAkbbACmwNLOyjJHDI,4036
2598
+ invar/node_tools/eslint-plugin/rules/max-file-lines.js.map,sha256=XmXWsVlDt-M9r9cbN-ef_94XWNiYchOgTfF_VpAueh4,2631
2599
+ invar/node_tools/eslint-plugin/rules/max-function-lines.js,sha256=cphnqCRu7ttbEA4s8RhLnPJ9Xop6Gu3S4hQbKmv2xh0,4801
2600
+ invar/node_tools/eslint-plugin/rules/max-function-lines.js.map,sha256=U9lNUhJh8fZxDPsmrqfTYeC5XxsPpXcods1oC3rNlHg,3462
2601
+ invar/node_tools/eslint-plugin/rules/no-any-in-schema.js,sha256=iwnOpt46j6vVZ-_UE6QxQvbzNqDu0RjLjS-T4LNRsW0,1161
2602
+ invar/node_tools/eslint-plugin/rules/no-any-in-schema.js.map,sha256=gcyGufNyq-yg3vHXyERVtFDWuudAfrUnnAiTlUXP570,827
2603
+ invar/node_tools/eslint-plugin/rules/no-empty-schema.js,sha256=9cOvqoNfEJqFzPY2IFlil40JvsAoSNNbKm6JYCMSzXU,2665
2604
+ invar/node_tools/eslint-plugin/rules/no-empty-schema.js.map,sha256=LN3odR5hiItX5Xsx-cx3tp_puTf6OJTh1_w4GvzZ4ro,1630
2605
+ invar/node_tools/eslint-plugin/rules/no-impure-calls-in-core.js,sha256=cj-pkOYyOcdpiW2KFL2VdnElLsprCX8zkBh_OtrZgDQ,1871
2606
+ invar/node_tools/eslint-plugin/rules/no-impure-calls-in-core.js.map,sha256=AVjJyCG9wcDY49Et-98nFZWMkRoaBnHMNKQ7YdqJvZc,1140
2607
+ invar/node_tools/eslint-plugin/rules/no-io-in-core.js,sha256=GuLL2q4JQw48AMmm2bxIv6pb4wxoI5GW3wKWvNg4g-M,2828
2608
+ invar/node_tools/eslint-plugin/rules/no-io-in-core.js.map,sha256=24czZgT72KDPOgLLaU7XD-xWOGYtdnZ954NIg57Mppc,2142
2609
+ invar/node_tools/eslint-plugin/rules/no-pure-logic-in-shell.js,sha256=odeFcMdoGbT7btwRMRmKKe8AZbtj_t356aI0rLKoxF8,7204
2610
+ invar/node_tools/eslint-plugin/rules/no-pure-logic-in-shell.js.map,sha256=d69VN2b5spFdKgt6BTA1lPir1Ivic3Y86mzrvp9yhX4,4323
2611
+ invar/node_tools/eslint-plugin/rules/no-redundant-type-schema.js,sha256=vf8V7_T5-oZetb3WjHslwOg9R6TDjFzRJGUb5lIpMR0,4282
2612
+ invar/node_tools/eslint-plugin/rules/no-redundant-type-schema.js.map,sha256=EVxgRb6jWXsaJPE4nJpZBA72suG3kPyE637VdrHomdg,2758
2613
+ invar/node_tools/eslint-plugin/rules/no-runtime-imports.js,sha256=GxlLKHzid3eSbO5NOkrJi0KH_SGHZQWa22gcR22hClk,2297
2614
+ invar/node_tools/eslint-plugin/rules/no-runtime-imports.js.map,sha256=9bpNyMo9l3mIChn1sTWh40_dVYRaopUnUrnncvWEpmE,1348
2615
+ invar/node_tools/eslint-plugin/rules/require-complete-validation.js,sha256=ZGeAElzcIHsnNdMNb9wHShb8irCTkUhOBg-KIhV2Mes,3943
2616
+ invar/node_tools/eslint-plugin/rules/require-complete-validation.js.map,sha256=UhEr4fql0TObHt3ya8DydqbFlig5CSDFKIIJZSf74rA,2497
2617
+ invar/node_tools/eslint-plugin/rules/require-jsdoc-example.js,sha256=Ad_TUhE_ly7H_hXR-9z4-8Pljm4gzO3-ikrKlyrR8Qw,3251
2618
+ invar/node_tools/eslint-plugin/rules/require-jsdoc-example.js.map,sha256=uKqyODyMkvGigg15kee440mhn34BXZny2PtGzJL4vww,2216
2619
+ invar/node_tools/eslint-plugin/rules/require-schema-validation.js,sha256=gdJaRr9-j8ZmUr8-u_MnafeQ4I92OoIq3RzUXeqrpBg,12372
2620
+ invar/node_tools/eslint-plugin/rules/require-schema-validation.js.map,sha256=akTSZQq-OC6ZvwMklIBRaxsesJ9w6GHaPhRTJ_ihmaE,8211
2621
+ invar/node_tools/eslint-plugin/rules/shell-complexity.js,sha256=Bhc9i7ILYMNNAUvxf9l7PlwhC_Neb69Jl1nUNABu2jg,11260
2622
+ invar/node_tools/eslint-plugin/rules/shell-complexity.js.map,sha256=0oJoDxXpXg2VTrwGMa-UDZ6te15Sv9vq-LgwWjJB5Q4,7140
2623
+ invar/node_tools/eslint-plugin/rules/shell-result-type.js,sha256=_Qa56cltufODvYWvwk95ClnTVi7GCZwrFRS_pAxMH40,5018
2624
+ invar/node_tools/eslint-plugin/rules/shell-result-type.js.map,sha256=9v70u0btd440PGm2uzjenzJZ7n0thOY8O2Kh8_jy7WY,3505
2625
+ invar/node_tools/eslint-plugin/rules/thin-entry-points.js,sha256=_mR9OjUM60Ibd6jHTC66sK3jh1G9Zvv75pB2A9wIsVI,7474
2626
+ invar/node_tools/eslint-plugin/rules/thin-entry-points.js.map,sha256=SviSkTYZJpQh170_cqVsqCNX_Kb_bfXUPx1iv1KYRjQ,4546
2627
+ invar/node_tools/eslint-plugin/rules/__tests__/behavior.test.js,sha256=vVbNLBlMxfUKRu4VqmAEZL2PyszBpV4zdI-KaYAeAbc,46153
2628
+ invar/node_tools/eslint-plugin/rules/__tests__/behavior.test.js.map,sha256=8fmKAURaFrL94zWKv9FY0MJxAe8SsA-J5hXFi-7Q1zY,24028
2629
+ invar/node_tools/eslint-plugin/rules/__tests__/e2e-scenarios.test.js,sha256=TzZh0koQLjNAQCuZ10xX4G9vr5Sx0cwU8TxJvg80DlI,15176
2630
+ invar/node_tools/eslint-plugin/rules/__tests__/e2e-scenarios.test.js.map,sha256=qSoqS-LKyuruILbr9ifljGIXup1gCw7jE-vKz4qCB2Q,5543
2631
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/exported-functions.js,sha256=IX6M8WLbh9SHnyeR_LOnAefytAHneoeRGwtx8W7pbgA,917
2632
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/exported-functions.js.map,sha256=iWANZtMzbuyfTurCuxJHI7oyxkNUfcAlT73gHo0z_jY,621
2633
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/function-lengths.js,sha256=5NQwJMXTqz9NEVLQpTKw12uSN7tG9UUY2AL9009UoP0,2765
2634
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/function-lengths.js.map,sha256=yOPff0TDS5nF1FPmtnxex0f1HsyRFaqFY6vwZiq94dM,3949
2635
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/has-io-imports.js,sha256=HUbi1HO4IWikd1AKGe73zaubCija1Ry7ga5oVehZgSo,392
2636
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/has-io-imports.js.map,sha256=ZyoMSaEUP2HFnRD7QgHKwkoLin21R0-8yAGt0mPN35Y,332
2637
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/valid-small.js,sha256=HUCHklyeTmD22zYz7qbPiRakVgO6ndiU2jUZmEd2bek,493
2638
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/core/valid-small.js.map,sha256=8CG7SnU4oQHFeFFtSVYPvg60NbaB4pBZ8SJdkMetWZE,436
2639
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/shell/with-io.js,sha256=a4X5AR48mqrkIqBKzLFwvZ7z_GH3vUPU1ZMI2K7bGbw,676
2640
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/shell/with-io.js.map,sha256=BFNEM57wHqatf_ljTpnarOn0EO_NFS7m4wDk3CMhBT8,575
2641
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/tests/large.test.js,sha256=XrrIrRSLbMYseu77-fXFkYW7Rsor1MiLNchWU71lFRc,6609
2642
+ invar/node_tools/eslint-plugin/rules/__tests__/fixtures/tests/large.test.js.map,sha256=iQ3v5eFK5aohl3eS6WyUWqiXOu3Q4-0pIrLxclUNsSg,10451
2643
+ invar/node_tools/eslint-plugin/utils/layer-detection.js,sha256=uzZfWtjsuZjy5vYz_94BxxDeNg32VZeparqthsuijB0,2722
2644
+ invar/node_tools/eslint-plugin/utils/layer-detection.js.map,sha256=bJ-AZeU9Urh13hA5JGPF4r5cjr2wnbpCIhdZn5_x1gU,1591
2645
+ invar/node_tools/eslint-plugin/utils/math-example.js,sha256=LUl7lPlVK9hW9RpfAl3laEDlqWb6aVXda8er44bWpoM,457
2646
+ invar/node_tools/eslint-plugin/utils/math-example.js.map,sha256=UR2zrFklTl347aXIOwAd4Z6VdhWpxApsz7Gpi1l9cyk,326
2597
2647
  invar/node_tools/fc-runner/bundle.js,sha256=D1wDqXYDOwWU4eqXeZdLIFvSsJ36q56Ay_ipLdklQPw,243285
2598
2648
  invar/node_tools/fc-runner/cli.d.ts,sha256=UM1WM4OKVvABXIsTHt6VHdO_oQMlgltUFV6CIqeh1B0,294
2599
2649
  invar/node_tools/fc-runner/cli.d.ts.map,sha256=-R1m4uJf2vmDZEpU7pEaib6TVjd0YuQnTQEGwkN6I9Q,117
@@ -2631,23 +2681,23 @@ invar/shell/ts_compiler.py,sha256=nA8brnOhThj9J_J3vAEGjDsM4NjbWQ_eX8Yf4pHPOgk,66
2631
2681
  invar/shell/commands/__init__.py,sha256=MEkKwVyjI9DmkvBpJcuumXo2Pg_FFkfEr-Rr3nrAt7A,284
2632
2682
  invar/shell/commands/doc.py,sha256=SOLDoCXXGxx_JU0PKXlAIGEF36PzconHmmAtL-rM6D4,13819
2633
2683
  invar/shell/commands/feedback.py,sha256=lLxEeWW_71US_vlmorFrGXS8IARB9nbV6D0zruLs660,7640
2634
- invar/shell/commands/guard.py,sha256=xTQ8cPp-x1xMCtufKxmMNUSpIpH31uUjziAB8ifCnC0,24837
2684
+ invar/shell/commands/guard.py,sha256=ZuqxYkCZHoomUPuk4-HNf0bTk86il0_uRkhOTvD6T3k,24840
2635
2685
  invar/shell/commands/hooks.py,sha256=W-SOnT4VQyUvXwipozkJwgEYfiOJGz7wksrbcdWegUg,2356
2636
2686
  invar/shell/commands/init.py,sha256=rtoPFsfq7xRZ6lfTipWT1OejNK5wfzqu1ncXi1kizU0,23634
2637
2687
  invar/shell/commands/merge.py,sha256=nuvKo8m32-OL-SCQlS4SLKmOZxQ3qj-1nGCx1Pgzifw,8183
2638
2688
  invar/shell/commands/mutate.py,sha256=GwemiO6LlbGCBEQsBFnzZuKhF-wIMEl79GAMnKUWc8U,5765
2639
- invar/shell/commands/perception.py,sha256=Vl6zgxkqtS3QRXBat6U_utNhpViyPFoPh899-OtDLgQ,19493
2689
+ invar/shell/commands/perception.py,sha256=mMAf6B0HSJq5WnZc7dlkQ3CJKAqUyVR97VAvTFGe6Jc,20276
2640
2690
  invar/shell/commands/skill.py,sha256=oKVyaxQ_LK28FpJhRpBDpXcpRdUBK3n6rC0qD77ax1M,5803
2641
2691
  invar/shell/commands/sync_self.py,sha256=nmqBry7V2_enKwy2zzHg8UoedZNicLe3yKDhjmBeZ68,3880
2642
2692
  invar/shell/commands/template_sync.py,sha256=aNWyFPMFT7pSwHrvwGCqcKAwb4dp7S9tvZzy9H4gAnw,16094
2643
- invar/shell/commands/test.py,sha256=goMf-ovvzEyWQMheq4YlJ-mwK5-w3lDj0cq0IA_1-_c,4205
2693
+ invar/shell/commands/test.py,sha256=eXTuqjBhxpQrkWQE54blQvbRsILhq5JIAGbl453UoTo,4207
2644
2694
  invar/shell/commands/uninstall.py,sha256=X5wWT41RFCdXkZ2aZEwLWy9oF1ajp9UyiO1O2iR6DrQ,20188
2645
2695
  invar/shell/commands/update.py,sha256=-NNQQScEb_0bV1B8YFxTjpUECk9c8dGAiNVRc64nWhY,1008
2646
2696
  invar/shell/prove/__init__.py,sha256=ZqlbmyMFJf6yAle8634jFuPRv8wNvHps8loMlOJyf8A,240
2647
2697
  invar/shell/prove/accept.py,sha256=cnY_6jzU1EBnpLF8-zWUWcXiSXtCwxPsXEYXsSVPG38,3717
2648
2698
  invar/shell/prove/cache.py,sha256=jbNdrvfLjvK7S0iqugErqeabb4YIbQuwIlcSRyCKbcg,4105
2649
2699
  invar/shell/prove/crosshair.py,sha256=XhJDsQWIriX9SuqeflUYvJgp9gJTDH7I7Uka6zjNzZ0,16734
2650
- invar/shell/prove/guard_ts.py,sha256=4wY2QyyOXJ6eRjHWd6M4l866i3_o6AmfffFQVq7Q5xU,35886
2700
+ invar/shell/prove/guard_ts.py,sha256=OKw5Pfr4PbTVnFAixe5WhScyxWr4FiHabSTvjiFqLGs,36948
2651
2701
  invar/shell/prove/hypothesis.py,sha256=QUclOOUg_VB6wbmHw8O2EPiL5qBOeBRqQeM04AVuLw0,9880
2652
2702
  invar/templates/CLAUDE.md.template,sha256=eaGU3SyRO_NEifw5b26k3srgQH4jyeujjCJ-HbM36_w,4913
2653
2703
  invar/templates/__init__.py,sha256=cb3ht8KPK5oBn5oG6HsTznujmo9WriJ_P--fVxJwycc,45
@@ -2728,10 +2778,10 @@ invar/templates/skills/invar-reflect/template.md,sha256=Rr5hvbllvmd8jSLf_0ZjyKt6
2728
2778
  invar/templates/skills/investigate/SKILL.md.jinja,sha256=cp6TBEixBYh1rLeeHOR1yqEnFqv1NZYePORMnavLkQI,3231
2729
2779
  invar/templates/skills/propose/SKILL.md.jinja,sha256=6BuKiCqO1AEu3VtzMHy1QWGqr_xqG9eJlhbsKT4jev4,3463
2730
2780
  invar/templates/skills/review/SKILL.md.jinja,sha256=ET5mbdSe_eKgJbi2LbgFC-z1aviKcHOBw7J5Q28fr4U,14105
2731
- invar_tools-1.17.2.dist-info/METADATA,sha256=0Ia55RdUQIH2J--ojW9QUjqRfWgR2rlY9oLOyCPgEZE,28595
2732
- invar_tools-1.17.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
2733
- invar_tools-1.17.2.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
2734
- invar_tools-1.17.2.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
2735
- invar_tools-1.17.2.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
2736
- invar_tools-1.17.2.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
2737
- invar_tools-1.17.2.dist-info/RECORD,,
2781
+ invar_tools-1.17.5.dist-info/METADATA,sha256=7SJ527V22NzbubWOXTd-oEDfHMg9p_eo_R1gNlkGfDg,28595
2782
+ invar_tools-1.17.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
2783
+ invar_tools-1.17.5.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
2784
+ invar_tools-1.17.5.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
2785
+ invar_tools-1.17.5.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
2786
+ invar_tools-1.17.5.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
2787
+ invar_tools-1.17.5.dist-info/RECORD,,