invar-tools 1.0.0__py3-none-any.whl → 1.3.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.
Files changed (98) hide show
  1. invar/__init__.py +1 -0
  2. invar/core/contracts.py +80 -10
  3. invar/core/entry_points.py +367 -0
  4. invar/core/extraction.py +5 -6
  5. invar/core/format_specs.py +195 -0
  6. invar/core/format_strategies.py +197 -0
  7. invar/core/formatter.py +32 -10
  8. invar/core/hypothesis_strategies.py +50 -10
  9. invar/core/inspect.py +1 -1
  10. invar/core/lambda_helpers.py +3 -2
  11. invar/core/models.py +30 -18
  12. invar/core/must_use.py +2 -1
  13. invar/core/parser.py +13 -6
  14. invar/core/postcondition_scope.py +128 -0
  15. invar/core/property_gen.py +86 -42
  16. invar/core/purity.py +13 -7
  17. invar/core/purity_heuristics.py +5 -9
  18. invar/core/references.py +8 -6
  19. invar/core/review_trigger.py +370 -0
  20. invar/core/rule_meta.py +69 -2
  21. invar/core/rules.py +91 -28
  22. invar/core/shell_analysis.py +247 -0
  23. invar/core/shell_architecture.py +171 -0
  24. invar/core/strategies.py +7 -14
  25. invar/core/suggestions.py +92 -0
  26. invar/core/sync_helpers.py +238 -0
  27. invar/core/tautology.py +103 -37
  28. invar/core/template_parser.py +467 -0
  29. invar/core/timeout_inference.py +4 -7
  30. invar/core/utils.py +63 -18
  31. invar/core/verification_routing.py +155 -0
  32. invar/mcp/server.py +113 -13
  33. invar/shell/commands/__init__.py +11 -0
  34. invar/shell/{cli.py → commands/guard.py} +152 -44
  35. invar/shell/{init_cmd.py → commands/init.py} +200 -28
  36. invar/shell/commands/merge.py +256 -0
  37. invar/shell/commands/mutate.py +184 -0
  38. invar/shell/{perception.py → commands/perception.py} +2 -0
  39. invar/shell/commands/sync_self.py +113 -0
  40. invar/shell/commands/template_sync.py +366 -0
  41. invar/shell/{test_cmd.py → commands/test.py} +3 -1
  42. invar/shell/commands/update.py +48 -0
  43. invar/shell/config.py +247 -10
  44. invar/shell/coverage.py +351 -0
  45. invar/shell/fs.py +5 -2
  46. invar/shell/git.py +2 -0
  47. invar/shell/guard_helpers.py +116 -20
  48. invar/shell/guard_output.py +106 -24
  49. invar/shell/mcp_config.py +3 -0
  50. invar/shell/mutation.py +314 -0
  51. invar/shell/property_tests.py +75 -24
  52. invar/shell/prove/__init__.py +9 -0
  53. invar/shell/prove/accept.py +113 -0
  54. invar/shell/{prove.py → prove/crosshair.py} +69 -30
  55. invar/shell/prove/hypothesis.py +293 -0
  56. invar/shell/subprocess_env.py +393 -0
  57. invar/shell/template_engine.py +345 -0
  58. invar/shell/templates.py +53 -0
  59. invar/shell/testing.py +77 -37
  60. invar/templates/CLAUDE.md.template +86 -9
  61. invar/templates/aider.conf.yml.template +16 -14
  62. invar/templates/commands/audit.md +138 -0
  63. invar/templates/commands/guard.md +77 -0
  64. invar/templates/config/CLAUDE.md.jinja +206 -0
  65. invar/templates/config/context.md.jinja +92 -0
  66. invar/templates/config/pre-commit.yaml.jinja +44 -0
  67. invar/templates/context.md.template +33 -0
  68. invar/templates/cursorrules.template +25 -13
  69. invar/templates/examples/README.md +2 -0
  70. invar/templates/examples/conftest.py +3 -0
  71. invar/templates/examples/contracts.py +4 -2
  72. invar/templates/examples/core_shell.py +10 -4
  73. invar/templates/examples/workflow.md +81 -0
  74. invar/templates/manifest.toml +137 -0
  75. invar/templates/protocol/INVAR.md +210 -0
  76. invar/templates/skills/develop/SKILL.md.jinja +318 -0
  77. invar/templates/skills/investigate/SKILL.md.jinja +106 -0
  78. invar/templates/skills/propose/SKILL.md.jinja +104 -0
  79. invar/templates/skills/review/SKILL.md.jinja +125 -0
  80. invar_tools-1.3.0.dist-info/METADATA +377 -0
  81. invar_tools-1.3.0.dist-info/RECORD +95 -0
  82. invar_tools-1.3.0.dist-info/entry_points.txt +2 -0
  83. invar_tools-1.3.0.dist-info/licenses/LICENSE +190 -0
  84. invar_tools-1.3.0.dist-info/licenses/LICENSE-GPL +674 -0
  85. invar_tools-1.3.0.dist-info/licenses/NOTICE +63 -0
  86. invar/contracts.py +0 -152
  87. invar/decorators.py +0 -94
  88. invar/invariant.py +0 -57
  89. invar/resource.py +0 -99
  90. invar/shell/prove_fallback.py +0 -183
  91. invar/shell/update_cmd.py +0 -191
  92. invar/templates/INVAR.md +0 -134
  93. invar_tools-1.0.0.dist-info/METADATA +0 -321
  94. invar_tools-1.0.0.dist-info/RECORD +0 -64
  95. invar_tools-1.0.0.dist-info/entry_points.txt +0 -2
  96. invar_tools-1.0.0.dist-info/licenses/LICENSE +0 -21
  97. /invar/shell/{prove_cache.py → prove/cache.py} +0 -0
  98. {invar_tools-1.0.0.dist-info → invar_tools-1.3.0.dist-info}/WHEEL +0 -0
invar/shell/update_cmd.py DELETED
@@ -1,191 +0,0 @@
1
- """
2
- Update command for Invar.
3
-
4
- Shell module: handles updating Invar-managed files to latest version.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import re
10
- from pathlib import Path
11
-
12
- import typer
13
- from returns.result import Failure, Result, Success
14
- from rich.console import Console
15
-
16
- from invar.shell.templates import copy_examples_directory, get_template_path
17
-
18
- console = Console()
19
-
20
- # Version pattern: matches "v3.23" or "v3.23.1"
21
- VERSION_PATTERN = re.compile(r"v(\d+)\.(\d+)(?:\.(\d+))?")
22
-
23
-
24
- def parse_version(text: str) -> tuple[int, int, int] | None:
25
- """
26
- Parse version string from text.
27
-
28
- >>> parse_version("Protocol v3.23")
29
- (3, 23, 0)
30
- >>> parse_version("v3.23.1")
31
- (3, 23, 1)
32
- >>> parse_version("no version here")
33
- """
34
- match = VERSION_PATTERN.search(text)
35
- if match:
36
- major = int(match.group(1))
37
- minor = int(match.group(2))
38
- patch = int(match.group(3)) if match.group(3) else 0
39
- return (major, minor, patch)
40
- return None
41
-
42
-
43
- def get_current_version(path: Path) -> Result[tuple[int, int, int], str]:
44
- """Get version from current INVAR.md file."""
45
- invar_md = path / "INVAR.md"
46
- if not invar_md.exists():
47
- return Failure("INVAR.md not found. Run 'invar init' first.")
48
-
49
- try:
50
- content = invar_md.read_text()
51
- version = parse_version(content)
52
- if version is None:
53
- return Failure("Could not parse version from INVAR.md")
54
- return Success(version)
55
- except OSError as e:
56
- return Failure(f"Failed to read INVAR.md: {e}")
57
-
58
-
59
- def get_template_version() -> Result[tuple[int, int, int], str]:
60
- """Get version from template INVAR.md."""
61
- template_result = get_template_path("INVAR.md")
62
- if isinstance(template_result, Failure):
63
- return template_result
64
-
65
- template_path = template_result.unwrap()
66
- try:
67
- content = template_path.read_text()
68
- version = parse_version(content)
69
- if version is None:
70
- return Failure("Could not parse version from template")
71
- return Success(version)
72
- except OSError as e:
73
- return Failure(f"Failed to read template: {e}")
74
-
75
-
76
- def format_version(version: tuple[int, int, int]) -> str:
77
- """Format version tuple as string."""
78
- if version[2] == 0:
79
- return f"v{version[0]}.{version[1]}"
80
- return f"v{version[0]}.{version[1]}.{version[2]}"
81
-
82
-
83
- def update_invar_md(path: Path, console: Console) -> Result[bool, str]:
84
- """Update INVAR.md by overwriting with template."""
85
- template_result = get_template_path("INVAR.md")
86
- if isinstance(template_result, Failure):
87
- return template_result
88
-
89
- template_path = template_result.unwrap()
90
- dest_file = path / "INVAR.md"
91
-
92
- try:
93
- dest_file.write_text(template_path.read_text())
94
- return Success(True)
95
- except OSError as e:
96
- return Failure(f"Failed to update INVAR.md: {e}")
97
-
98
-
99
- def update_examples(path: Path, console: Console) -> Result[bool, str]:
100
- """Update .invar/examples/ directory."""
101
- import shutil
102
-
103
- examples_dest = path / ".invar" / "examples"
104
-
105
- # Remove existing examples
106
- if examples_dest.exists():
107
- try:
108
- shutil.rmtree(examples_dest)
109
- except OSError as e:
110
- return Failure(f"Failed to remove old examples: {e}")
111
-
112
- # Copy new examples
113
- return copy_examples_directory(path, console)
114
-
115
-
116
- def update(
117
- path: Path = typer.Argument(Path(), help="Project root directory"),
118
- force: bool = typer.Option(
119
- False, "--force", "-f", help="Update even if already at latest version"
120
- ),
121
- check: bool = typer.Option(
122
- False, "--check", help="Check for updates without applying"
123
- ),
124
- ) -> None:
125
- """
126
- Update Invar-managed files to latest version.
127
-
128
- Updates INVAR.md and .invar/examples/ from the installed python-invar package.
129
- User-managed files (CLAUDE.md, .invar/context.md) are never modified.
130
-
131
- Use --check to see if updates are available without applying them.
132
- Use --force to update even if already at latest version.
133
- """
134
- # Get current version
135
- current_result = get_current_version(path)
136
- if isinstance(current_result, Failure):
137
- console.print(f"[red]Error:[/red] {current_result.failure()}")
138
- raise typer.Exit(1)
139
- current_version = current_result.unwrap()
140
-
141
- # Get template version
142
- template_result = get_template_version()
143
- if isinstance(template_result, Failure):
144
- console.print(f"[red]Error:[/red] {template_result.failure()}")
145
- raise typer.Exit(1)
146
- template_version = template_result.unwrap()
147
-
148
- current_str = format_version(current_version)
149
- template_str = format_version(template_version)
150
-
151
- # Compare versions
152
- needs_update = template_version > current_version
153
-
154
- if check:
155
- # Check mode: just report status
156
- if needs_update:
157
- console.print(f"[yellow]Update available:[/yellow] {current_str} → {template_str}")
158
- else:
159
- console.print(f"[green]Up to date:[/green] {current_str}")
160
- return
161
-
162
- if not needs_update and not force:
163
- console.print(f"[green]Already at latest version:[/green] {current_str}")
164
- console.print("[dim]Use --force to update anyway[/dim]")
165
- return
166
-
167
- # Perform update
168
- console.print("\n[bold]Updating Invar files...[/bold]")
169
- console.print(f" Version: {current_str} → {template_str}")
170
- console.print()
171
-
172
- # Update INVAR.md
173
- result = update_invar_md(path, console)
174
- if isinstance(result, Failure):
175
- console.print(f"[red]Error:[/red] {result.failure()}")
176
- raise typer.Exit(1)
177
- console.print(f"[green]Updated[/green] INVAR.md ({template_str})")
178
-
179
- # Update examples
180
- result = update_examples(path, console)
181
- if isinstance(result, Failure):
182
- console.print(f"[yellow]Warning:[/yellow] {result.failure()}")
183
- else:
184
- console.print("[green]Updated[/green] .invar/examples/")
185
-
186
- # Remind about user-managed files
187
- console.print()
188
- console.print("[dim]User-managed files unchanged:[/dim]")
189
- console.print("[dim] ○ CLAUDE.md[/dim]")
190
- console.print("[dim] ○ .invar/context.md[/dim]")
191
- console.print("[dim] ○ pyproject.toml [tool.invar][/dim]")
invar/templates/INVAR.md DELETED
@@ -1,134 +0,0 @@
1
- <!--
2
- ┌─────────────────────────────────────────────────────────────┐
3
- │ INVAR-MANAGED FILE - DO NOT EDIT DIRECTLY │
4
- │ │
5
- │ This file is managed by Invar. Changes may be lost on │
6
- │ `invar update`. Add project content to CLAUDE.md instead. │
7
- └─────────────────────────────────────────────────────────────┘
8
- -->
9
- # The Invar Protocol v3.26
10
-
11
- > **"Trade structure for safety."**
12
-
13
- ## Six Laws
14
-
15
- | Law | Principle |
16
- |-----|-----------|
17
- | 1. Separation | Core (pure logic) / Shell (I/O) physically separate |
18
- | 2. Contract Complete | @pre/@post + doctests uniquely determine implementation |
19
- | 3. Context Economy | map → sig → code (only read what's needed) |
20
- | 4. Decompose First | Break into sub-functions before implementing |
21
- | 5. Verify Reflectively | Fail → Reflect (why?) → Fix → Verify |
22
- | 6. Integrate Fully | Local correct ≠ Global correct; verify all paths |
23
-
24
- ## Core/Shell Architecture
25
-
26
- | Zone | Location | Requirements |
27
- |------|----------|--------------|
28
- | Core | `**/core/**` | @pre/@post, pure (no I/O), doctests |
29
- | Shell | `**/shell/**` | `Result[T, E]` return type |
30
-
31
- **Forbidden in Core:** `os`, `sys`, `subprocess`, `pathlib`, `open`, `requests`, `datetime.now`
32
-
33
- ## Core Example (Pure Logic)
34
-
35
- ```python
36
- from deal import pre, post
37
-
38
- @pre(lambda price, discount: price > 0 and 0 <= discount <= 1)
39
- @post(lambda result: result >= 0)
40
- def discounted_price(price: float, discount: float) -> float:
41
- """
42
- >>> discounted_price(100, 0.2)
43
- 80.0
44
- >>> discounted_price(100, 0) # Edge: no discount
45
- 100.0
46
- """
47
- return price * (1 - discount)
48
- ```
49
-
50
- **Self-test:** Can someone else write the exact same function from just @pre/@post + doctests?
51
-
52
- ## Shell Example (I/O Operations)
53
-
54
- ```python
55
- from pathlib import Path
56
- from returns.result import Result, Success, Failure
57
-
58
- def read_config(path: Path) -> Result[dict, str]:
59
- """Shell: handles I/O, returns Result for error handling."""
60
- try:
61
- import json
62
- return Success(json.loads(path.read_text()))
63
- except FileNotFoundError:
64
- return Failure(f"File not found: {path}")
65
- except json.JSONDecodeError as e:
66
- return Failure(f"Invalid JSON: {e}")
67
- ```
68
-
69
- **Pattern:** Shell reads file → passes content to Core → returns Result.
70
-
71
- More examples: `.invar/examples/`
72
-
73
- ## Session Start (Required)
74
-
75
- Before writing any code, execute:
76
-
77
- 1. **invar_guard** (changed=true) — Check existing violations
78
- 2. **invar_map** (top=10) — Understand code structure
79
-
80
- Then read:
81
- - `.invar/examples/` — Core/Shell patterns
82
- - `.invar/context.md` — Project state, lessons learned
83
-
84
- **Skipping these steps → Non-compliant code → Rework required.**
85
-
86
- Use MCP tools if available (`invar_guard`, `invar_map`), otherwise use CLI commands.
87
- For agent-specific entry format, see your configuration file (CLAUDE.md, .cursorrules, etc).
88
-
89
- ## ICIDIV Workflow (Required Order)
90
-
91
- ```
92
- 1. Intent — What? Core or Shell? Edge cases?
93
- 2. Contract — @pre/@post + doctests BEFORE code
94
- 3. Inspect — invar sig <file>, invar map --top 10
95
- 4. Design — Decompose: leaves first, then compose
96
- 5. Implement — Write code to pass your doctests
97
- 6. Verify — invar guard. If fail: reflect → fix → verify
98
- ```
99
-
100
- **Contract before Implement. Verify after every change. No exceptions.**
101
-
102
- ## Task Completion
103
-
104
- A task is complete only when ALL conditions are met:
105
- - Session Start executed (invar_guard + invar_map, context read)
106
- - Intent explicitly stated
107
- - Contract written before implementation
108
- - Final **invar_guard** passed
109
- - User requirement satisfied
110
-
111
- **Missing any = Task incomplete.**
112
-
113
- ## Commands
114
-
115
- ```bash
116
- invar guard # Full: static + doctests + CrossHair + Hypothesis (default)
117
- invar guard --static # Static only (quick debug, ~0.5s)
118
- invar guard --changed # Modified files only
119
- invar sig <file> # Show contracts + signatures
120
- invar map --top 10 # Most-referenced symbols
121
- ```
122
-
123
- ## Configuration
124
-
125
- ```toml
126
- [tool.invar.guard]
127
- core_paths = ["src/myapp/core"]
128
- shell_paths = ["src/myapp/shell"]
129
- exclude_doctest_lines = true # Don't count doctests in function size
130
- ```
131
-
132
- ---
133
-
134
- *Protocol v3.26 | [Guide](docs/INVAR-GUIDE.md) | [Examples](.invar/examples/)*
@@ -1,321 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: invar-tools
3
- Version: 1.0.0
4
- Summary: AI-native software engineering tools with design-by-contract verification
5
- Project-URL: Homepage, https://github.com/tefx/invar
6
- Project-URL: Documentation, https://github.com/tefx/invar#readme
7
- Project-URL: Repository, https://github.com/tefx/invar
8
- Project-URL: Issues, https://github.com/tefx/invar/issues
9
- Author: Invar Team
10
- License-Expression: MIT
11
- License-File: LICENSE
12
- Keywords: ai,code-quality,contracts,design-by-contract,static-analysis
13
- Classifier: Development Status :: 3 - Alpha
14
- Classifier: Intended Audience :: Developers
15
- Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Topic :: Software Development :: Quality Assurance
20
- Classifier: Typing :: Typed
21
- Requires-Python: >=3.11
22
- Requires-Dist: crosshair-tool>=0.0.60
23
- Requires-Dist: hypothesis>=6.0
24
- Requires-Dist: invar-runtime>=1.0
25
- Requires-Dist: mcp>=1.0
26
- Requires-Dist: pre-commit>=3.0
27
- Requires-Dist: pydantic>=2.0
28
- Requires-Dist: returns>=0.20
29
- Requires-Dist: rich>=13.0
30
- Requires-Dist: typer>=0.9
31
- Provides-Extra: dev
32
- Requires-Dist: mypy>=1.0; extra == 'dev'
33
- Requires-Dist: pytest-cov>=4.0; extra == 'dev'
34
- Requires-Dist: pytest>=7.0; extra == 'dev'
35
- Requires-Dist: ruff>=0.1; extra == 'dev'
36
- Description-Content-Type: text/markdown
37
-
38
- # Invar
39
-
40
- [![PyPI version](https://badge.fury.io/py/invar-tools.svg)](https://badge.fury.io/py/invar-tools)
41
- [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
42
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
43
-
44
- **Don't hope AI code is correct. Know it.**
45
-
46
- Invar is a framework that helps developers ensure AI-generated code is reliable through contracts, verification, and mechanical checks.
47
-
48
- ```python
49
- from invar_runtime import pre, post
50
-
51
- @pre(lambda items: len(items) > 0)
52
- @post(lambda result: result >= 0)
53
- def average(items: list[float]) -> float:
54
- """
55
- >>> average([1, 2, 3])
56
- 2.0
57
- """
58
- return sum(items) / len(items)
59
- ```
60
-
61
- ---
62
-
63
- ## Installation
64
-
65
- ### Development Tools
66
-
67
- ```bash
68
- # Recommended: use without installing
69
- uvx invar-tools guard
70
-
71
- # Or install globally
72
- pip install invar-tools
73
- ```
74
-
75
- ### Runtime Contracts (for your project)
76
-
77
- ```bash
78
- pip install invar-runtime
79
- ```
80
-
81
- **Packages:**
82
-
83
- | Package | Size | Purpose |
84
- |---------|------|---------|
85
- | `invar-runtime` | ~3MB | Runtime contracts (`@pre`, `@post`, etc.) |
86
- | `invar-tools` | ~100MB | Development tools (`guard`, `map`, `sig`, MCP) |
87
-
88
- ---
89
-
90
- ## Quick Start
91
-
92
- ```bash
93
- cd your-project
94
- invar init # Creates INVAR.md, CLAUDE.md, .invar/
95
- invar guard # Verify code quality
96
- ```
97
-
98
- **Multi-Agent Support:** `invar init` auto-detects and configures:
99
-
100
- | AI Tool | Configuration |
101
- |---------|---------------|
102
- | Claude Code | Creates CLAUDE.md + MCP server |
103
- | Cursor | Adds to .cursorrules |
104
- | Aider | Adds to .aider.conf.yml |
105
- | Others | Add "Follow INVAR.md" to system prompt |
106
-
107
- ---
108
-
109
- ## Core/Shell Architecture
110
-
111
- Invar enforces separation between pure logic and I/O:
112
-
113
- | Zone | Requirements | Forbidden |
114
- |------|--------------|-----------|
115
- | **Core** | `@pre`/`@post` contracts, doctests | I/O imports (os, pathlib, requests...) |
116
- | **Shell** | `Result[T, E]` returns | - |
117
-
118
- ```python
119
- # Core: Pure logic, receives data
120
- def parse_config(content: str) -> Config:
121
- return Config.parse(content)
122
-
123
- # Shell: Handles I/O, returns Result
124
- def load_config(path: Path) -> Result[Config, str]:
125
- content = path.read_text()
126
- return Success(parse_config(content))
127
- ```
128
-
129
- ---
130
-
131
- ## Commands
132
-
133
- ### Guard (Primary)
134
-
135
- ```bash
136
- invar guard # Full verification (default)
137
- invar guard --changed # Only git-modified files
138
- invar guard --static # Static only (~0.5s)
139
- ```
140
-
141
- **Flags:**
142
-
143
- | Flag | Purpose |
144
- |------|---------|
145
- | `--strict` | Treat warnings as errors |
146
- | `--explain` | Show rule limitations |
147
- | `--agent` | JSON output for AI tools |
148
- | `--pedantic` | Show all rules including off-by-default |
149
-
150
- ### Other Commands
151
-
152
- ```bash
153
- invar sig <file> # Show signatures + contracts
154
- invar map --top 10 # Most-referenced symbols
155
- invar rules # List all rules
156
- invar update # Update managed files
157
- ```
158
-
159
- ---
160
-
161
- ## Verification Levels (DX-19)
162
-
163
- | Level | Command | Checks | When to Use |
164
- |-------|---------|--------|-------------|
165
- | STATIC | `--static` | Rules only (~0.5s) | Debugging static analysis |
166
- | STANDARD | (default) | Rules + doctests + CrossHair + Hypothesis (~5s) | Everything else |
167
-
168
- **Zero decisions:** Default runs full verification. Incremental mode makes it fast (~5s first, ~2s cached).
169
-
170
- ---
171
-
172
- ## Configuration
173
-
174
- ### pyproject.toml
175
-
176
- ```toml
177
- [tool.invar.guard]
178
- # Directory classification
179
- core_paths = ["src/myapp/core"]
180
- shell_paths = ["src/myapp/shell"]
181
-
182
- # Or use patterns for existing projects
183
- core_patterns = ["**/domain/**", "**/models/**"]
184
- shell_patterns = ["**/api/**", "**/cli/**"]
185
-
186
- # Size limits
187
- max_file_lines = 500
188
- max_function_lines = 50
189
-
190
- # Contract requirements
191
- require_contracts = true
192
- require_doctests = true
193
-
194
- # Doctest-heavy code
195
- exclude_doctest_lines = true
196
-
197
- # Purity overrides
198
- purity_pure = ["pandas.DataFrame.groupby"]
199
- purity_impure = ["my_module.cached_lookup"]
200
-
201
- # Rule exclusions
202
- [[tool.invar.guard.rule_exclusions]]
203
- pattern = "**/generated/**"
204
- rules = ["*"]
205
-
206
- # Severity overrides
207
- [tool.invar.guard.severity_overrides]
208
- redundant_type_contract = "off"
209
- ```
210
-
211
- ---
212
-
213
- ## Rules Reference
214
-
215
- | Rule | Severity | What It Checks |
216
- |------|----------|----------------|
217
- | `file_size` | ERROR | File > max lines |
218
- | `function_size` | WARN | Function > max lines |
219
- | `missing_contract` | ERROR | Core function lacks @pre/@post |
220
- | `missing_doctest` | WARN | Contracted function lacks doctest |
221
- | `forbidden_import` | ERROR | I/O import in Core |
222
- | `shell_result` | WARN | Shell function not returning Result |
223
- | `empty_contract` | ERROR | Contract is `lambda: True` |
224
- | `param_mismatch` | ERROR | Lambda params ≠ function params |
225
-
226
- Full list: `invar rules --explain`
227
-
228
- ---
229
-
230
- ## MCP Integration (Claude Code)
231
-
232
- `invar init` automatically creates `.mcp.json` with smart detection of available methods:
233
-
234
- ```json
235
- {
236
- "mcpServers": {
237
- "invar": {
238
- "command": "uvx",
239
- "args": ["invar-tools", "mcp"]
240
- }
241
- }
242
- }
243
- ```
244
-
245
- **Claude Code Integration:**
246
-
247
- ```bash
248
- # Full integration (recommended)
249
- invar init --claude
250
-
251
- # Specify MCP method
252
- invar init --claude --mcp-method uvx # Recommended
253
- invar init --claude --mcp-method command # Use PATH invar
254
- invar init --claude --mcp-method python # Use current Python
255
- ```
256
-
257
- **MCP Tools:**
258
-
259
- | Tool | Replaces | Purpose |
260
- |------|----------|---------|
261
- | `invar_guard` | `pytest`, `crosshair` | Smart Guard verification |
262
- | `invar_sig` | Reading entire file | Show contracts and signatures |
263
- | `invar_map` | `grep` for functions | Symbol map with reference counts |
264
-
265
- Manual setup: See `.invar/mcp-setup.md` after running `invar init`.
266
-
267
- ---
268
-
269
- ## File Ownership
270
-
271
- | File | Owner | Edit? |
272
- |------|-------|-------|
273
- | `INVAR.md` | Invar | No (`invar update` manages) |
274
- | `CLAUDE.md` | You | Yes (project config) |
275
- | `.invar/examples/` | Invar | No (reference only) |
276
-
277
- ---
278
-
279
- ## Runtime Behavior
280
-
281
- Contracts are checked at runtime via [deal](https://github.com/life4/deal).
282
-
283
- ```bash
284
- # Disable in production
285
- DEAL_DISABLE=1 python app.py
286
- ```
287
-
288
- ---
289
-
290
- ## Troubleshooting
291
-
292
- **Guard is slow:**
293
- - Use `--changed` for incremental checks
294
- - Use `--static` to skip doctests during debugging
295
-
296
- **Too many warnings:**
297
- - Add exclusions for generated code
298
- - Override severity for noisy rules
299
-
300
- **CrossHair timeout:**
301
- - Some code patterns don't work well with symbolic execution
302
- - Hypothesis fallback runs automatically
303
-
304
- ---
305
-
306
- ## Learn More
307
-
308
- **In your project** (created by `invar init`):
309
- - `INVAR.md` — Protocol for AI agents
310
- - `CLAUDE.md` — Project configuration
311
- - `.invar/examples/` — Reference patterns
312
-
313
- **Documentation:**
314
- - [docs/INVAR-GUIDE.md](./docs/INVAR-GUIDE.md) — Detailed guide
315
- - [docs/VISION.md](./docs/VISION.md) — Design philosophy
316
-
317
- ---
318
-
319
- ## License
320
-
321
- MIT