invar-tools 1.17.7__py3-none-any.whl → 1.17.8__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.
invar/mcp/handlers.py CHANGED
@@ -13,6 +13,7 @@ import sys
13
13
  from pathlib import Path
14
14
  from typing import Any, Literal
15
15
 
16
+ from mcp.server.lowlevel.server import StructuredContent, CombinationContent
16
17
  from mcp.types import TextContent
17
18
  from returns.result import Success
18
19
 
@@ -59,7 +60,7 @@ def _validate_path(path: str) -> tuple[bool, str]:
59
60
  # @shell_orchestration: MCP handler - subprocess is called inside
60
61
  # @shell_complexity: Guard command with multiple optional flags
61
62
  # @invar:allow shell_result: MCP handler for guard tool
62
- async def _run_guard(args: dict[str, Any]) -> list[TextContent]:
63
+ async def _run_guard(args: dict[str, Any]) -> list[TextContent] | CombinationContent:
63
64
  """Run invar guard command."""
64
65
  path = args.get("path", ".")
65
66
  is_valid, error = _validate_path(path)
@@ -88,7 +89,7 @@ async def _run_guard(args: dict[str, Any]) -> list[TextContent]:
88
89
 
89
90
  # @shell_orchestration: MCP handler - subprocess is called inside
90
91
  # @invar:allow shell_result: MCP handler for sig tool
91
- async def _run_sig(args: dict[str, Any]) -> list[TextContent]:
92
+ async def _run_sig(args: dict[str, Any]) -> list[TextContent] | CombinationContent:
92
93
  """Run invar sig command."""
93
94
  target = args.get("target", "")
94
95
  if not target:
@@ -106,7 +107,7 @@ async def _run_sig(args: dict[str, Any]) -> list[TextContent]:
106
107
 
107
108
  # @shell_orchestration: MCP handler - subprocess is called inside
108
109
  # @invar:allow shell_result: MCP handler for map tool
109
- async def _run_map(args: dict[str, Any]) -> list[TextContent]:
110
+ async def _run_map(args: dict[str, Any]) -> list[TextContent] | CombinationContent:
110
111
  """Run invar map command."""
111
112
  path = args.get("path", ".")
112
113
  is_valid, error = _validate_path(path)
@@ -125,7 +126,7 @@ async def _run_map(args: dict[str, Any]) -> list[TextContent]:
125
126
 
126
127
  # @shell_orchestration: MCP handler - orchestrates refs command execution
127
128
  # @invar:allow shell_result: MCP handler for refs tool
128
- async def _run_refs(args: dict[str, Any]) -> list[TextContent]:
129
+ async def _run_refs(args: dict[str, Any]) -> list[TextContent] | CombinationContent:
129
130
  """Run invar refs command.
130
131
 
131
132
  DX-78: Find all references to a symbol.
@@ -155,7 +156,7 @@ async def _run_refs(args: dict[str, Any]) -> list[TextContent]:
155
156
  # @shell_orchestration: MCP handler - calls shell layer directly
156
157
  # @shell_complexity: MCP input validation + result handling
157
158
  # @invar:allow shell_result: MCP handler for doc_toc tool
158
- async def _run_doc_toc(args: dict[str, Any]) -> list[TextContent]:
159
+ async def _run_doc_toc(args: dict[str, Any]) -> list[TextContent] | CombinationContent:
159
160
  """Run invar_doc_toc - extract document structure."""
160
161
  from dataclasses import asdict
161
162
 
@@ -203,7 +204,7 @@ def _section_to_dict(section: Any) -> dict[str, Any]:
203
204
  # @shell_orchestration: MCP handler - calls shell layer directly
204
205
  # @shell_complexity: MCP input validation + result handling
205
206
  # @invar:allow shell_result: MCP handler for doc_read tool
206
- async def _run_doc_read(args: dict[str, Any]) -> list[TextContent]:
207
+ async def _run_doc_read(args: dict[str, Any]) -> list[TextContent] | CombinationContent:
207
208
  """Run invar_doc_read - read a specific section."""
208
209
  from invar.shell.doc_tools import read_section
209
210
 
@@ -232,7 +233,7 @@ async def _run_doc_read(args: dict[str, Any]) -> list[TextContent]:
232
233
 
233
234
  # @shell_complexity: Multiple arg validation branches + error handling
234
235
  # @invar:allow shell_result: MCP handler for doc_read_many tool
235
- async def _run_doc_read_many(args: dict[str, Any]) -> list[TextContent]:
236
+ async def _run_doc_read_many(args: dict[str, Any]) -> list[TextContent] | CombinationContent:
236
237
  """Run invar_doc_read_many - read multiple sections."""
237
238
  from invar.shell.doc_tools import read_sections_batch
238
239
 
@@ -264,7 +265,7 @@ async def _run_doc_read_many(args: dict[str, Any]) -> list[TextContent]:
264
265
  # @shell_orchestration: MCP handler - calls shell layer directly
265
266
  # @shell_complexity: MCP input validation + result handling
266
267
  # @invar:allow shell_result: MCP handler for doc_find tool
267
- async def _run_doc_find(args: dict[str, Any]) -> list[TextContent]:
268
+ async def _run_doc_find(args: dict[str, Any]) -> list[TextContent] | CombinationContent:
268
269
  """Run invar_doc_find - find sections matching pattern."""
269
270
  from invar.shell.doc_tools import find_sections
270
271
 
@@ -308,7 +309,7 @@ async def _run_doc_find(args: dict[str, Any]) -> list[TextContent]:
308
309
  # @shell_orchestration: MCP handler - calls shell layer directly
309
310
  # @shell_complexity: MCP input validation + result handling
310
311
  # @invar:allow shell_result: MCP handler for doc_replace tool
311
- async def _run_doc_replace(args: dict[str, Any]) -> list[TextContent]:
312
+ async def _run_doc_replace(args: dict[str, Any]) -> list[TextContent] | CombinationContent:
312
313
  """Run invar_doc_replace - replace section content."""
313
314
  from invar.shell.doc_tools import replace_section_content
314
315
 
@@ -342,7 +343,7 @@ async def _run_doc_replace(args: dict[str, Any]) -> list[TextContent]:
342
343
  # @shell_orchestration: MCP handler - calls shell layer directly
343
344
  # @shell_complexity: MCP input validation + result handling
344
345
  # @invar:allow shell_result: MCP handler for doc_insert tool
345
- async def _run_doc_insert(args: dict[str, Any]) -> list[TextContent]:
346
+ async def _run_doc_insert(args: dict[str, Any]) -> list[TextContent] | CombinationContent:
346
347
  """Run invar_doc_insert - insert content relative to section."""
347
348
  from invar.shell.doc_tools import insert_section_content
348
349
 
@@ -382,7 +383,7 @@ async def _run_doc_insert(args: dict[str, Any]) -> list[TextContent]:
382
383
  # @shell_orchestration: MCP handler - calls shell layer directly
383
384
  # @shell_complexity: MCP input validation + result handling
384
385
  # @invar:allow shell_result: MCP handler for doc_delete tool
385
- async def _run_doc_delete(args: dict[str, Any]) -> list[TextContent]:
386
+ async def _run_doc_delete(args: dict[str, Any]) -> list[TextContent] | CombinationContent:
386
387
  """Run invar_doc_delete - delete a section."""
387
388
  from invar.shell.doc_tools import delete_section_content
388
389
 
@@ -411,13 +412,11 @@ async def _run_doc_delete(args: dict[str, Any]) -> list[TextContent]:
411
412
 
412
413
  # @shell_complexity: Command execution with error handling branches
413
414
  # @invar:allow shell_result: MCP subprocess wrapper utility
414
- async def _execute_command(cmd: list[str], timeout: int = 600) -> list[TextContent]:
415
- """Execute a command and return the result.
416
-
417
- Args:
418
- cmd: Command to execute
419
- timeout: Maximum time in seconds (default: 600, accommodates full Guard cycle)
420
- """
415
+ async def _execute_command(
416
+ cmd: list[str],
417
+ timeout: int = 600,
418
+ ) -> list[TextContent] | CombinationContent:
419
+ """Execute a command and return result."""
421
420
  try:
422
421
  result = subprocess.run(
423
422
  cmd,
@@ -426,20 +425,16 @@ async def _execute_command(cmd: list[str], timeout: int = 600) -> list[TextConte
426
425
  timeout=timeout,
427
426
  )
428
427
 
429
- output = result.stdout
430
- if result.stderr:
431
- output += f"\n\nStderr:\n{result.stderr}"
432
-
433
- # Try to parse as JSON for better formatting
434
428
  try:
435
429
  parsed = json.loads(result.stdout)
436
- output = json.dumps(parsed, indent=2)
430
+ return ([TextContent(type="text", text=json.dumps(parsed, indent=2))], parsed)
437
431
  except json.JSONDecodeError:
438
- pass
439
-
440
- return [TextContent(type="text", text=output)]
432
+ output = result.stdout
433
+ if result.stderr:
434
+ output += f"\n\nStderr:\n{result.stderr}"
435
+ return [TextContent(type="text", text=output)]
441
436
 
442
437
  except subprocess.TimeoutExpired:
443
- return [TextContent(type="text", text=f"Error: Command timed out ({timeout}s)")]
438
+ return [TextContent(type="text", text=f"Error: Command timed out ({timeout}s")]
444
439
  except Exception as e:
445
440
  return [TextContent(type="text", text=f"Error: {e}")]
@@ -15,12 +15,6 @@ from rich.console import Console
15
15
  from rich.table import Table
16
16
 
17
17
 
18
- def _detect_agent_mode() -> bool:
19
- """Detect agent context: INVAR_MODE=agent OR non-TTY (pipe/redirect)."""
20
- import sys
21
- return os.getenv("INVAR_MODE") == "agent" or not sys.stdout.isatty()
22
-
23
-
24
18
  from invar import __version__
25
19
  from invar.core.models import GuardReport, RuleConfig
26
20
  from invar.core.rules import check_all_rules
@@ -95,12 +89,14 @@ def _scan_and_check(
95
89
  for rule, reason, line in extract_escape_hatches(file_info.source):
96
90
  all_escapes.append((file_info.path, rule, reason))
97
91
  # DX-66: Add to escape hatch summary
98
- report.escape_hatches.add(EscapeHatchDetail(
99
- file=file_info.path,
100
- line=line,
101
- rule=rule,
102
- reason=reason,
103
- ))
92
+ report.escape_hatches.add(
93
+ EscapeHatchDetail(
94
+ file=file_info.path,
95
+ line=line,
96
+ rule=rule,
97
+ reason=reason,
98
+ )
99
+ )
104
100
 
105
101
  # DX-22: Check project-level complexity debt (Fix-or-Explain enforcement)
106
102
  for debt_violation in check_complexity_debt(
@@ -133,7 +129,7 @@ def guard(
133
129
  False, "--static", help="Static analysis only, skip all runtime tests"
134
130
  ),
135
131
  human: bool = typer.Option(
136
- False, "--human", help="Force human-readable output (for testing/debugging)"
132
+ False, "--human", help="Force Rich human-readable output (opt-in, default is JSON)"
137
133
  ),
138
134
  # DX-26: Deprecated flags kept for backward compatibility
139
135
  no_strict_pure: bool = typer.Option(
@@ -192,23 +188,12 @@ def guard(
192
188
  ts_result = run_typescript_guard(path if path.is_dir() else find_project_root(path))
193
189
  match ts_result:
194
190
  case Success(result):
195
- if json_output or agent:
196
- import json as json_mod
197
-
198
- from invar.shell.prove.guard_ts import format_typescript_guard_v2
199
-
200
- output = format_typescript_guard_v2(result)
201
- console.print(json_mod.dumps(output, indent=2))
202
- else:
203
- console.print(f"[bold]TypeScript Guard[/bold] ({project_language})")
204
- if result.status == "passed":
205
- console.print("[green]✓ PASSED[/green]")
206
- elif result.status == "skipped":
207
- console.print("[yellow]⚠ SKIPPED[/yellow] (no TypeScript tools available)")
208
- else:
209
- console.print(f"[red]✗ FAILED[/red] ({result.error_count} errors)")
210
- for v in result.violations[:10]: # Show first 10
211
- console.print(f" {v.file}:{v.line}: [{v.severity}] {v.message}")
191
+ import json as json_mod
192
+
193
+ from invar.shell.prove.guard_ts import format_typescript_guard_v2
194
+
195
+ output = format_typescript_guard_v2(result)
196
+ console.print(json_mod.dumps(output, indent=2))
212
197
  raise typer.Exit(0 if result.status == "passed" else 1)
213
198
  case Failure(err):
214
199
  console.print(f"[red]Error:[/red] {err}")
@@ -295,6 +280,7 @@ def guard(
295
280
  run_pattern_detection,
296
281
  suggestions_to_violations,
297
282
  )
283
+
298
284
  # Run pattern detection on checked files
299
285
  files_to_check = list(only_files) if only_files else None
300
286
  pattern_result = run_pattern_detection(path, files_to_check)
@@ -331,6 +317,7 @@ def guard(
331
317
  # DX-37: Check coverage availability if requested
332
318
  if coverage:
333
319
  from invar.shell.coverage import check_coverage_available
320
+
334
321
  cov_check = check_coverage_available()
335
322
  if isinstance(cov_check, Failure):
336
323
  console.print(f"[yellow]Warning:[/yellow] {cov_check.failure()}")
@@ -342,14 +329,19 @@ def guard(
342
329
 
343
330
  # Phase 1: Doctests (DX-37: with optional coverage)
344
331
  doctest_passed, doctest_output, doctest_coverage = run_doctests_phase(
345
- checked_files, explain, timeout=config.timeout_doctest,
332
+ checked_files,
333
+ explain,
334
+ timeout=config.timeout_doctest,
346
335
  collect_coverage=coverage,
347
336
  )
348
337
 
349
338
  # Phase 2: CrossHair symbolic verification
350
339
  # Note: CrossHair uses subprocess + symbolic execution, coverage not applicable
351
340
  crosshair_passed, crosshair_output = run_crosshair_phase(
352
- path, checked_files, doctest_passed, static_exit_code,
341
+ path,
342
+ checked_files,
343
+ doctest_passed,
344
+ static_exit_code,
353
345
  changed_mode=changed,
354
346
  timeout=config.timeout_crosshair,
355
347
  per_condition_timeout=config.timeout_crosshair_per_condition,
@@ -357,7 +349,9 @@ def guard(
357
349
 
358
350
  # Phase 3: Hypothesis property tests (DX-37: with optional coverage)
359
351
  property_passed, property_output, property_coverage = run_property_tests_phase(
360
- checked_files, doctest_passed, static_exit_code,
352
+ checked_files,
353
+ doctest_passed,
354
+ static_exit_code,
361
355
  collect_coverage=coverage,
362
356
  )
363
357
  elif verification_level == VerificationLevel.STATIC:
@@ -382,20 +376,31 @@ def guard(
382
376
  if property_coverage and property_coverage.get("collected"):
383
377
  coverage_output["phases_tracked"].append("hypothesis")
384
378
  if "overall_branch_coverage" in property_coverage:
385
- coverage_output["overall_branch_coverage"] = property_coverage["overall_branch_coverage"]
379
+ coverage_output["overall_branch_coverage"] = property_coverage[
380
+ "overall_branch_coverage"
381
+ ]
386
382
 
387
383
  # DX-26: Unified output (agent JSON or human Rich)
388
384
  if use_agent_output:
389
385
  output_agent(
390
- report, strict, doctest_passed, doctest_output, crosshair_output, level_name,
386
+ report,
387
+ strict,
388
+ doctest_passed,
389
+ doctest_output,
390
+ crosshair_output,
391
+ level_name,
391
392
  property_output=property_output,
392
393
  coverage_data=coverage_output, # DX-37
393
394
  )
394
395
  else:
395
396
  output_rich(report, config.strict_pure, changed, pedantic, explain, static)
396
397
  output_verification_status(
397
- verification_level, static_exit_code, doctest_passed,
398
- doctest_output, crosshair_output, explain,
398
+ verification_level,
399
+ static_exit_code,
400
+ doctest_passed,
401
+ doctest_output,
402
+ crosshair_output,
403
+ explain,
399
404
  property_output=property_output,
400
405
  strict=strict,
401
406
  )
@@ -405,7 +410,9 @@ def guard(
405
410
  overall = coverage_output.get("overall_branch_coverage", 0.0)
406
411
  console.print(f"\n[bold]Coverage Analysis[/bold] ({' + '.join(phases)})")
407
412
  console.print(f" Overall branch coverage: {overall}%")
408
- console.print(" [dim]Note: CrossHair uses symbolic execution; coverage not applicable.[/dim]")
413
+ console.print(
414
+ " [dim]Note: CrossHair uses symbolic execution; coverage not applicable.[/dim]"
415
+ )
409
416
 
410
417
  # Exit with combined status
411
418
  all_passed = doctest_passed and crosshair_passed and property_passed
@@ -415,24 +422,23 @@ def guard(
415
422
 
416
423
  # @shell_orchestration: Output mode decision helper for CLI
417
424
  def _determine_output_mode(human: bool, agent: bool = False, json_output: bool = False) -> bool:
418
- """Determine if agent JSON output should be used (DX-26).
425
+ """Determine if agent JSON output should be used (Agent First).
419
426
 
420
- DX-26: TTY auto-detection with --human override.
421
- - --human flag human output (for testing/debugging)
422
- - TTY (terminal) → human output
423
- - Non-TTY (pipe/redirect) → agent JSON output
424
- - Deprecated --agent/--json flagsstill work for backward compat
427
+ Agent First principle: Machine-readable JSON is the default output format.
428
+ Human-readable Rich output is opt-in via --human flag.
429
+
430
+ Priority:
431
+ - --human flaghuman output (Rich, colored)
432
+ - Default → JSON output (machine-readable, Agent Native)
433
+ - --agent/--json flags → no-op (already default, kept for backward compat)
425
434
  """
426
- # --human flag always forces human output
435
+ # --human flag forces human output (priority highest)
427
436
  if human:
428
437
  return False # use_agent = False
429
438
 
430
- # Deprecated flags (backward compat)
431
- if json_output or agent:
432
- return True # use_agent = True
433
-
434
- # TTY auto-detection
435
- return _detect_agent_mode() # Returns True if non-TTY
439
+ # Default to JSON output (Agent First)
440
+ # --agent/--json are now no-ops but kept for backward compatibility
441
+ return True
436
442
 
437
443
 
438
444
  def _show_verification_level(verification_level) -> None:
@@ -464,8 +470,9 @@ def map_command(
464
470
  """Generate symbol map with reference counts."""
465
471
  from invar.shell.commands.perception import run_map
466
472
 
467
- # Phase 9 P11: Auto-detect agent mode
468
- use_json = json_output or _detect_agent_mode()
473
+ # Agent First: Default to JSON output
474
+ # --json is now a no-op (kept for backward compat)
475
+ use_json = True
469
476
  result = run_map(path, top, use_json)
470
477
  if isinstance(result, Failure):
471
478
  console.print(f"[red]Error:[/red] {result.failure()}")
@@ -480,8 +487,9 @@ def sig_command(
480
487
  """Extract signatures from a file or symbol."""
481
488
  from invar.shell.commands.perception import run_sig
482
489
 
483
- # Phase 9 P11: Auto-detect agent mode
484
- use_json = json_output or _detect_agent_mode()
490
+ # Agent First: Default to JSON output
491
+ # --json is now a no-op (kept for backward compat)
492
+ use_json = True
485
493
  result = run_sig(target, use_json)
486
494
  if isinstance(result, Failure):
487
495
  console.print(f"[red]Error:[/red] {result.failure()}")
@@ -504,8 +512,9 @@ def refs_command(
504
512
  """
505
513
  from invar.shell.commands.perception import run_refs
506
514
 
507
- # Auto-detect agent mode
508
- use_json = json_output or _detect_agent_mode()
515
+ # Agent First: Default to JSON output
516
+ # --json is now a no-op (kept for backward compat)
517
+ use_json = True
509
518
  result = run_refs(target, use_json)
510
519
  if isinstance(result, Failure):
511
520
  console.print(f"[red]Error:[/red] {result.failure()}")
@@ -529,8 +538,9 @@ def rules(
529
538
 
530
539
  from invar.core.rule_meta import RULE_META, RuleCategory, get_rules_by_category
531
540
 
532
- # Phase 9 P11: Auto-detect agent mode
533
- use_json = json_output or _detect_agent_mode()
541
+ # Agent First: Default to JSON output
542
+ # --json is now a no-op (kept for backward compat)
543
+ use_json = True
534
544
 
535
545
  # Filter by category if specified
536
546
  if category:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invar-tools
3
- Version: 1.17.7
3
+ Version: 1.17.8
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
@@ -50,7 +50,7 @@ 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=Kls1aXnYmGcMvib_Mesfz5FjBaL7lmrhKaw_P2JDnyw,16551
53
+ invar/mcp/handlers.py,sha256=gutZuiukdfANWJliRtqX0xV7__MDwMs8C1U3H-R_hDs,16736
54
54
  invar/mcp/server.py,sha256=zSpY9bCFuq4mWe7XfolTnwHffhdmoyN40aFL4L7dFrE,20407
55
55
  invar/node_tools/.gitignore,sha256=M2kz8Iw7Kzmi44mKo1r7_HOZMh79a7dFDdRrqXyaEhI,530
56
56
  invar/node_tools/MANIFEST,sha256=2Z2at-27MK8K7DSjOjjtR4faTbt6eCiKQuEfvP_lwH8,145
@@ -2681,7 +2681,7 @@ invar/shell/ts_compiler.py,sha256=nA8brnOhThj9J_J3vAEGjDsM4NjbWQ_eX8Yf4pHPOgk,66
2681
2681
  invar/shell/commands/__init__.py,sha256=MEkKwVyjI9DmkvBpJcuumXo2Pg_FFkfEr-Rr3nrAt7A,284
2682
2682
  invar/shell/commands/doc.py,sha256=SOLDoCXXGxx_JU0PKXlAIGEF36PzconHmmAtL-rM6D4,13819
2683
2683
  invar/shell/commands/feedback.py,sha256=lLxEeWW_71US_vlmorFrGXS8IARB9nbV6D0zruLs660,7640
2684
- invar/shell/commands/guard.py,sha256=ZuqxYkCZHoomUPuk4-HNf0bTk86il0_uRkhOTvD6T3k,24840
2684
+ invar/shell/commands/guard.py,sha256=oVx8h0YVxngfvEiuSmSuDTuXXKpzBzOHz5_5nD8_Ua4,24374
2685
2685
  invar/shell/commands/hooks.py,sha256=W-SOnT4VQyUvXwipozkJwgEYfiOJGz7wksrbcdWegUg,2356
2686
2686
  invar/shell/commands/init.py,sha256=rtoPFsfq7xRZ6lfTipWT1OejNK5wfzqu1ncXi1kizU0,23634
2687
2687
  invar/shell/commands/merge.py,sha256=nuvKo8m32-OL-SCQlS4SLKmOZxQ3qj-1nGCx1Pgzifw,8183
@@ -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.7.dist-info/METADATA,sha256=v4uFpW9-ouRRiRqmkIR-GRWDTDsLa5304LnjooU67sA,28595
2782
- invar_tools-1.17.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
2783
- invar_tools-1.17.7.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
2784
- invar_tools-1.17.7.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
2785
- invar_tools-1.17.7.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
2786
- invar_tools-1.17.7.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
2787
- invar_tools-1.17.7.dist-info/RECORD,,
2781
+ invar_tools-1.17.8.dist-info/METADATA,sha256=2wbwXUo9G-81P-Riuv9WTYBY06Yiihint4wbdIvlJoE,28595
2782
+ invar_tools-1.17.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
2783
+ invar_tools-1.17.8.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
2784
+ invar_tools-1.17.8.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
2785
+ invar_tools-1.17.8.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
2786
+ invar_tools-1.17.8.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
2787
+ invar_tools-1.17.8.dist-info/RECORD,,