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.
- invar/__init__.py +1 -0
- invar/core/contracts.py +80 -10
- invar/core/entry_points.py +367 -0
- invar/core/extraction.py +5 -6
- invar/core/format_specs.py +195 -0
- invar/core/format_strategies.py +197 -0
- invar/core/formatter.py +32 -10
- invar/core/hypothesis_strategies.py +50 -10
- invar/core/inspect.py +1 -1
- invar/core/lambda_helpers.py +3 -2
- invar/core/models.py +30 -18
- invar/core/must_use.py +2 -1
- invar/core/parser.py +13 -6
- invar/core/postcondition_scope.py +128 -0
- invar/core/property_gen.py +86 -42
- invar/core/purity.py +13 -7
- invar/core/purity_heuristics.py +5 -9
- invar/core/references.py +8 -6
- invar/core/review_trigger.py +370 -0
- invar/core/rule_meta.py +69 -2
- invar/core/rules.py +91 -28
- invar/core/shell_analysis.py +247 -0
- invar/core/shell_architecture.py +171 -0
- invar/core/strategies.py +7 -14
- invar/core/suggestions.py +92 -0
- invar/core/sync_helpers.py +238 -0
- invar/core/tautology.py +103 -37
- invar/core/template_parser.py +467 -0
- invar/core/timeout_inference.py +4 -7
- invar/core/utils.py +63 -18
- invar/core/verification_routing.py +155 -0
- invar/mcp/server.py +113 -13
- invar/shell/commands/__init__.py +11 -0
- invar/shell/{cli.py → commands/guard.py} +152 -44
- invar/shell/{init_cmd.py → commands/init.py} +200 -28
- invar/shell/commands/merge.py +256 -0
- invar/shell/commands/mutate.py +184 -0
- invar/shell/{perception.py → commands/perception.py} +2 -0
- invar/shell/commands/sync_self.py +113 -0
- invar/shell/commands/template_sync.py +366 -0
- invar/shell/{test_cmd.py → commands/test.py} +3 -1
- invar/shell/commands/update.py +48 -0
- invar/shell/config.py +247 -10
- invar/shell/coverage.py +351 -0
- invar/shell/fs.py +5 -2
- invar/shell/git.py +2 -0
- invar/shell/guard_helpers.py +116 -20
- invar/shell/guard_output.py +106 -24
- invar/shell/mcp_config.py +3 -0
- invar/shell/mutation.py +314 -0
- invar/shell/property_tests.py +75 -24
- invar/shell/prove/__init__.py +9 -0
- invar/shell/prove/accept.py +113 -0
- invar/shell/{prove.py → prove/crosshair.py} +69 -30
- invar/shell/prove/hypothesis.py +293 -0
- invar/shell/subprocess_env.py +393 -0
- invar/shell/template_engine.py +345 -0
- invar/shell/templates.py +53 -0
- invar/shell/testing.py +77 -37
- invar/templates/CLAUDE.md.template +86 -9
- invar/templates/aider.conf.yml.template +16 -14
- invar/templates/commands/audit.md +138 -0
- invar/templates/commands/guard.md +77 -0
- invar/templates/config/CLAUDE.md.jinja +206 -0
- invar/templates/config/context.md.jinja +92 -0
- invar/templates/config/pre-commit.yaml.jinja +44 -0
- invar/templates/context.md.template +33 -0
- invar/templates/cursorrules.template +25 -13
- invar/templates/examples/README.md +2 -0
- invar/templates/examples/conftest.py +3 -0
- invar/templates/examples/contracts.py +4 -2
- invar/templates/examples/core_shell.py +10 -4
- invar/templates/examples/workflow.md +81 -0
- invar/templates/manifest.toml +137 -0
- invar/templates/protocol/INVAR.md +210 -0
- invar/templates/skills/develop/SKILL.md.jinja +318 -0
- invar/templates/skills/investigate/SKILL.md.jinja +106 -0
- invar/templates/skills/propose/SKILL.md.jinja +104 -0
- invar/templates/skills/review/SKILL.md.jinja +125 -0
- invar_tools-1.3.0.dist-info/METADATA +377 -0
- invar_tools-1.3.0.dist-info/RECORD +95 -0
- invar_tools-1.3.0.dist-info/entry_points.txt +2 -0
- invar_tools-1.3.0.dist-info/licenses/LICENSE +190 -0
- invar_tools-1.3.0.dist-info/licenses/LICENSE-GPL +674 -0
- invar_tools-1.3.0.dist-info/licenses/NOTICE +63 -0
- invar/contracts.py +0 -152
- invar/decorators.py +0 -94
- invar/invariant.py +0 -57
- invar/resource.py +0 -99
- invar/shell/prove_fallback.py +0 -183
- invar/shell/update_cmd.py +0 -191
- invar/templates/INVAR.md +0 -134
- invar_tools-1.0.0.dist-info/METADATA +0 -321
- invar_tools-1.0.0.dist-info/RECORD +0 -64
- invar_tools-1.0.0.dist-info/entry_points.txt +0 -2
- invar_tools-1.0.0.dist-info/licenses/LICENSE +0 -21
- /invar/shell/{prove_cache.py → prove/cache.py} +0 -0
- {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
|
-
[](https://badge.fury.io/py/invar-tools)
|
|
41
|
-
[](https://www.python.org/downloads/)
|
|
42
|
-
[](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
|