invar-tools 1.17.12__py3-none-any.whl → 1.17.14__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
@@ -427,11 +427,24 @@ async def _execute_command(
427
427
  timeout=timeout,
428
428
  )
429
429
 
430
+ stdout = result.stdout.strip()
431
+
432
+ # Try to parse as JSON
430
433
  try:
431
- parsed = json.loads(result.stdout)
434
+ parsed = json.loads(stdout)
432
435
  return ([TextContent(type="text", text=json.dumps(parsed, indent=2))], parsed)
433
436
  except json.JSONDecodeError:
434
- output = result.stdout
437
+ # Try to fix unescaped newlines in JSON strings
438
+ # Guard/map commands may output multiline JSON with literal newlines
439
+ fixed = _fix_json_newlines(stdout)
440
+ try:
441
+ parsed = json.loads(fixed)
442
+ return ([TextContent(type="text", text=json.dumps(parsed, indent=2))], parsed)
443
+ except json.JSONDecodeError:
444
+ pass
445
+
446
+ # Fall back to text output
447
+ output = stdout
435
448
  if result.stderr:
436
449
  output += f"\n\nStderr:\n{result.stderr}"
437
450
  return [TextContent(type="text", text=output)]
@@ -440,3 +453,46 @@ async def _execute_command(
440
453
  return [TextContent(type="text", text=f"Error: Command timed out ({timeout}s)")]
441
454
  except Exception as e:
442
455
  return [TextContent(type="text", text=f"Error: {e}")]
456
+
457
+
458
+ # @invar:allow shell_too_complex: Simple state machine, 6 branches is minimal
459
+ # @invar:allow shell_pure_logic: No I/O, but called from shell context
460
+ # @invar:allow shell_result: Pure transformation, returns str not Result
461
+ def _fix_json_newlines(text: str) -> str:
462
+ """Fix unescaped newlines in JSON strings.
463
+
464
+ When subprocess outputs multiline JSON, newlines inside string values
465
+ are not escaped, causing json.loads() to fail. This function escapes them.
466
+
467
+ DX-33: Escape hatch for complex pure logic helper.
468
+ """
469
+ result = []
470
+ i = 0
471
+ while i < len(text):
472
+ if text[i] == '"':
473
+ # Inside a string - collect until closing quote
474
+ result.append('"')
475
+ i += 1
476
+ while i < len(text):
477
+ c = text[i]
478
+ if c == "\\" and i + 1 < len(text):
479
+ # Escaped character - keep as is
480
+ result.append("\\")
481
+ result.append(text[i + 1])
482
+ i += 2
483
+ elif c == '"':
484
+ # End of string
485
+ result.append('"')
486
+ i += 1
487
+ break
488
+ elif c == "\n" or c == "\r":
489
+ # Unescaped newline - escape it
490
+ result.append("\\n")
491
+ i += 1
492
+ else:
493
+ result.append(c)
494
+ i += 1
495
+ else:
496
+ result.append(text[i])
497
+ i += 1
498
+ return "".join(result)
@@ -190,12 +190,28 @@ def guard(
190
190
  ts_result = run_typescript_guard(path if path.is_dir() else find_project_root(path))
191
191
  match ts_result:
192
192
  case Success(result):
193
- import json as json_mod
193
+ if human:
194
+ # Human-readable Rich output
195
+ from invar.shell.prove.guard_ts import format_typescript_guard_v2
196
+
197
+ output = format_typescript_guard_v2(result)
198
+ console.print(f"[bold]TypeScript Guard[/bold] ({project_language})")
199
+ if result.status == "passed":
200
+ console.print("[green]✓ PASSED[/green]")
201
+ elif result.status == "skipped":
202
+ console.print("[yellow]⚠ SKIPPED[/yellow] (no TypeScript tools available)")
203
+ else:
204
+ console.print(f"[red]✗ FAILED[/red] ({result.error_count} errors)")
205
+ for v in result.violations[:10]: # Show first 10
206
+ console.print(f" {v.file}:{v.line}: [{v.severity}] {v.message}")
207
+ else:
208
+ # JSON output for agents
209
+ import json as json_mod
194
210
 
195
- from invar.shell.prove.guard_ts import format_typescript_guard_v2
211
+ from invar.shell.prove.guard_ts import format_typescript_guard_v2
196
212
 
197
- output = format_typescript_guard_v2(result)
198
- console.print(json_mod.dumps(output, indent=2))
213
+ output = format_typescript_guard_v2(result)
214
+ console.print(json_mod.dumps(output, indent=2))
199
215
  raise typer.Exit(0 if result.status == "passed" else 1)
200
216
  case Failure(err):
201
217
  console.print(f"[red]Error:[/red] {err}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invar-tools
3
- Version: 1.17.12
3
+ Version: 1.17.14
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=3khWBATWxV4TwHPm6Gnr9pWfGaPyxf5p8yR-pnJhDtg,16756
53
+ invar/mcp/handlers.py,sha256=VQGpFG6voBOXML2TtBFyU_lkCTn83yF4GwmMvl2gxvI,18762
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=jlAExTLnOVUI_FCIViLHlqo1B_hVVC91h-Ac6xfv6HA,24433
2684
+ invar/shell/commands/guard.py,sha256=I1BDqthsAVmz8FnUgwkbhvy2cozmOfjs3rxQwwqFemo,25391
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.12.dist-info/METADATA,sha256=yPbRFLkoRTEBUOo-aLsAq9rf0UC3OzdMYWjdxXR-RFM,28596
2782
- invar_tools-1.17.12.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
2783
- invar_tools-1.17.12.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
2784
- invar_tools-1.17.12.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
2785
- invar_tools-1.17.12.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
2786
- invar_tools-1.17.12.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
2787
- invar_tools-1.17.12.dist-info/RECORD,,
2781
+ invar_tools-1.17.14.dist-info/METADATA,sha256=TC_Puck2FidWoZbAcQOGw7HmWg5F9fjAqY0mY-yRX-g,28596
2782
+ invar_tools-1.17.14.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
2783
+ invar_tools-1.17.14.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
2784
+ invar_tools-1.17.14.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
2785
+ invar_tools-1.17.14.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
2786
+ invar_tools-1.17.14.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
2787
+ invar_tools-1.17.14.dist-info/RECORD,,