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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Invar Pre-commit Hooks v{{ version }}
|
|
2
|
+
# Install: pre-commit install
|
|
3
|
+
#
|
|
4
|
+
# Default runs full verification (static + doctests + CrossHair + Hypothesis).
|
|
5
|
+
# Incremental mode makes this fast: ~5s first commit, ~2s subsequent (cached).
|
|
6
|
+
#
|
|
7
|
+
# Smart Guard: Detects rule-affecting file changes and runs full guard when needed.
|
|
8
|
+
# Structure Protection: Warns if INVAR.md is modified directly.
|
|
9
|
+
|
|
10
|
+
repos:
|
|
11
|
+
- repo: local
|
|
12
|
+
hooks:
|
|
13
|
+
# Warn if INVAR.md is modified directly (use --no-verify if intentional)
|
|
14
|
+
- id: invar-md-protected
|
|
15
|
+
name: INVAR.md Protection
|
|
16
|
+
entry: bash -c 'if git diff --cached --name-only | grep -q "^INVAR.md$"; then echo "Warning - INVAR.md was modified directly. Use git commit --no-verify if intentional."; exit 1; fi'
|
|
17
|
+
language: system
|
|
18
|
+
pass_filenames: false
|
|
19
|
+
stages: [pre-commit]
|
|
20
|
+
|
|
21
|
+
# Invar Guard (full verification by default)
|
|
22
|
+
# Uses incremental mode: only verifies changed files, caches results
|
|
23
|
+
# Smart mode: runs full guard when rule files change, --changed otherwise
|
|
24
|
+
- id: invar-guard
|
|
25
|
+
name: Invar Guard
|
|
26
|
+
entry: bash -c '
|
|
27
|
+
RULE_FILES="rule_meta.py rules.py contracts.py purity.py pyproject.toml"
|
|
28
|
+
STAGED=$(git diff --cached --name-only)
|
|
29
|
+
FULL=false
|
|
30
|
+
for f in $RULE_FILES; do
|
|
31
|
+
if echo "$STAGED" | grep -q "$f"; then FULL=true; break; fi
|
|
32
|
+
done
|
|
33
|
+
if [ "$FULL" = true ]; then
|
|
34
|
+
echo "⚠️ Rule change detected - running FULL guard"
|
|
35
|
+
invar guard
|
|
36
|
+
else
|
|
37
|
+
invar guard --changed
|
|
38
|
+
fi
|
|
39
|
+
'
|
|
40
|
+
language: python
|
|
41
|
+
additional_dependencies: ['invar-tools']
|
|
42
|
+
pass_filenames: false
|
|
43
|
+
stages: [pre-commit]
|
|
44
|
+
types: [python]
|
|
@@ -8,6 +8,39 @@
|
|
|
8
8
|
- Working on: [current task or feature]
|
|
9
9
|
- Blockers: None
|
|
10
10
|
|
|
11
|
+
## Key Rules (Quick Reference)
|
|
12
|
+
|
|
13
|
+
<!-- DX-54: Rules summary for long conversation resilience -->
|
|
14
|
+
|
|
15
|
+
### Core/Shell Separation
|
|
16
|
+
- **Core** (`**/core/**`): @pre/@post + doctests, NO I/O imports
|
|
17
|
+
- **Shell** (`**/shell/**`): Result[T, E] return type
|
|
18
|
+
|
|
19
|
+
### USBV Workflow
|
|
20
|
+
1. Understand → 2. Specify (contracts first) → 3. Build → 4. Validate
|
|
21
|
+
|
|
22
|
+
### Verification
|
|
23
|
+
- `invar guard` = static + doctests + CrossHair + Hypothesis
|
|
24
|
+
- Final must show: `✓ Final: guard PASS | ...`
|
|
25
|
+
|
|
26
|
+
## Self-Reminder
|
|
27
|
+
|
|
28
|
+
<!-- DX-54: AI should re-read this file periodically -->
|
|
29
|
+
|
|
30
|
+
**When to re-read this file:**
|
|
31
|
+
- Starting a new task
|
|
32
|
+
- Completing a task (before moving to next)
|
|
33
|
+
- Conversation has been going on for a while (~15-20 exchanges)
|
|
34
|
+
- Unsure about project rules or patterns
|
|
35
|
+
|
|
36
|
+
**Quick rule check:**
|
|
37
|
+
- Am I in Core or Shell?
|
|
38
|
+
- Do I have @pre/@post contracts?
|
|
39
|
+
- Am I following USBV workflow?
|
|
40
|
+
- Did I run guard before claiming "done"?
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
11
44
|
## Recent Decisions
|
|
12
45
|
|
|
13
46
|
1. [Decision summary] - [Brief rationale]
|
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
# Invar Protocol
|
|
2
2
|
|
|
3
|
-
Follow the Invar Protocol in INVAR.md — includes
|
|
3
|
+
Follow the Invar Protocol in INVAR.md — includes Check-In, USBV workflow, and Task Completion requirements.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Check-In
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Your first message MUST display:
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
|
|
11
|
-
invar_map (top=10) # Understand code structure
|
|
10
|
+
✓ Check-In: [project] | [branch] | [clean/dirty]
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
Actions:
|
|
14
|
+
1. Read `.invar/context.md` (Key Rules + Current State + Lessons Learned)
|
|
15
|
+
2. Show one-line status
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
**Do NOT execute guard or map at Check-In.**
|
|
18
|
+
Guard is for VALIDATE phase and Final only.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
This is your sign-in. The user sees it immediately.
|
|
21
|
+
No visible check-in = Session not started.
|
|
22
|
+
|
|
23
|
+
## Final
|
|
24
|
+
|
|
25
|
+
Your last message for an implementation task MUST display:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
✓ Final: guard PASS | 0 errors, 2 warnings
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Execute `invar guard` and show this one-line summary.
|
|
32
|
+
|
|
33
|
+
This is your sign-out. Completes the Check-In/Final pair.
|
|
21
34
|
|
|
22
35
|
## Quick Reference
|
|
23
36
|
|
|
24
37
|
- Core (`**/core/**`): @pre/@post contracts, doctests, pure (no I/O)
|
|
25
38
|
- Shell (`**/shell/**`): Result[T, E] return type
|
|
26
|
-
- Workflow:
|
|
27
|
-
-
|
|
28
|
-
- Task complete only when final invar_guard passes.
|
|
39
|
+
- Workflow: Understand → Specify → Build → Validate (USBV)
|
|
40
|
+
- Inspect before Contract. Depth varies naturally.
|
|
@@ -8,6 +8,7 @@ Reference examples for the Invar Protocol. These are managed by Invar.
|
|
|
8
8
|
|------|---------|
|
|
9
9
|
| [contracts.py](contracts.py) | @pre/@post patterns, doctest best practices |
|
|
10
10
|
| [core_shell.py](core_shell.py) | Core/Shell separation patterns |
|
|
11
|
+
| [workflow.md](workflow.md) | Visible USBV workflow example |
|
|
11
12
|
|
|
12
13
|
## Usage
|
|
13
14
|
|
|
@@ -15,6 +16,7 @@ Read these when you need:
|
|
|
15
16
|
- Contract pattern reference
|
|
16
17
|
- Core vs Shell decision guidance
|
|
17
18
|
- Doctest formatting examples
|
|
19
|
+
- USBV workflow example
|
|
18
20
|
|
|
19
21
|
---
|
|
20
22
|
|
|
@@ -5,6 +5,8 @@ Reference patterns for @pre/@post contracts and doctests.
|
|
|
5
5
|
Managed by Invar - do not edit directly.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
# For lambda-based contracts, use deal directly
|
|
9
|
+
# invar_runtime.pre/post are for Contract objects (NonEmpty, IsInstance, etc.)
|
|
8
10
|
from deal import post, pre
|
|
9
11
|
|
|
10
12
|
# =============================================================================
|
|
@@ -54,8 +56,8 @@ def average(items: list[float]) -> float:
|
|
|
54
56
|
# =============================================================================
|
|
55
57
|
|
|
56
58
|
|
|
57
|
-
@pre(lambda data:
|
|
58
|
-
@post(lambda result:
|
|
59
|
+
@pre(lambda data: len(data) > 0) # Non-empty input (type is in annotation)
|
|
60
|
+
@post(lambda result: len(result) > 0) # Preserves non-emptiness
|
|
59
61
|
def normalize_keys(data: dict[str, int]) -> dict[str, int]:
|
|
60
62
|
"""
|
|
61
63
|
Lowercase all keys.
|
|
@@ -7,6 +7,8 @@ Managed by Invar - do not edit directly.
|
|
|
7
7
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
+
# For lambda-based contracts, use deal directly
|
|
11
|
+
# invar_runtime.pre/post are for Contract objects (NonEmpty, IsInstance, etc.)
|
|
10
12
|
from deal import post, pre
|
|
11
13
|
from returns.result import Failure, Result, Success
|
|
12
14
|
|
|
@@ -18,8 +20,10 @@ from returns.result import Failure, Result, Success
|
|
|
18
20
|
# =============================================================================
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
@
|
|
22
|
-
@
|
|
23
|
+
# @invar:allow shell_result: Example file - demonstrates Core pattern
|
|
24
|
+
# @shell_orchestration: Example file - demonstrates Core pattern
|
|
25
|
+
@pre(lambda content: content is not None) # Accepts any string including empty
|
|
26
|
+
@post(lambda result: all(line.strip() == line and line for line in result)) # No whitespace, non-empty
|
|
23
27
|
def parse_lines(content: str) -> list[str]:
|
|
24
28
|
"""
|
|
25
29
|
Parse content into non-empty lines.
|
|
@@ -34,8 +38,10 @@ def parse_lines(content: str) -> list[str]:
|
|
|
34
38
|
return [line.strip() for line in content.split("\n") if line.strip()]
|
|
35
39
|
|
|
36
40
|
|
|
37
|
-
@
|
|
38
|
-
@
|
|
41
|
+
# @invar:allow shell_result: Example file - demonstrates Core pattern
|
|
42
|
+
# @shell_orchestration: Example file - demonstrates Core pattern
|
|
43
|
+
@pre(lambda items: all(isinstance(i, str) for i in items)) # All items must be strings
|
|
44
|
+
@post(lambda result: all(v > 0 for v in result.values())) # All counts are positive
|
|
39
45
|
def count_items(items: list[str]) -> dict[str, int]:
|
|
40
46
|
"""
|
|
41
47
|
Count occurrences of each item.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Visible Workflow Example
|
|
2
|
+
|
|
3
|
+
Complete example of USBV workflow with checkpoints for complex tasks.
|
|
4
|
+
|
|
5
|
+
## Scenario
|
|
6
|
+
|
|
7
|
+
User: "Add rate limiting to the API endpoints"
|
|
8
|
+
|
|
9
|
+
## Workflow Execution
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Agent: "Entering /develop for: Add rate limiting to API endpoints
|
|
13
|
+
|
|
14
|
+
✓ Check-In: MyApp | feature/rate-limit | clean
|
|
15
|
+
|
|
16
|
+
□ [UNDERSTAND]
|
|
17
|
+
- Task: Add rate limiting to API endpoints
|
|
18
|
+
- Scope: All routes in src/myapp/shell/api.py
|
|
19
|
+
- Constraint: Must be configurable per-route
|
|
20
|
+
- Existing: No rate limiting currently
|
|
21
|
+
|
|
22
|
+
□ [SPECIFY] RateLimiter class (Core)
|
|
23
|
+
@pre(lambda max_requests, window_seconds: max_requests > 0 and window_seconds > 0)
|
|
24
|
+
@post(lambda result: result is not None)
|
|
25
|
+
def is_allowed(self, key: str) -> bool:
|
|
26
|
+
'''
|
|
27
|
+
>>> limiter = RateLimiter(max_requests=2, window_seconds=60)
|
|
28
|
+
>>> limiter.is_allowed('user1')
|
|
29
|
+
True
|
|
30
|
+
>>> limiter.is_allowed('user1')
|
|
31
|
+
True
|
|
32
|
+
>>> limiter.is_allowed('user1') # 3rd request
|
|
33
|
+
False
|
|
34
|
+
'''
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
□ [SPECIFY] rate_limit decorator (Shell)
|
|
38
|
+
def rate_limit(max_requests: int, window_seconds: int):
|
|
39
|
+
'''Decorator for Flask routes. Returns Result[Response, RateLimitError].'''
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
[BUILD - implementing RateLimiter...]
|
|
43
|
+
[BUILD - implementing decorator...]
|
|
44
|
+
[BUILD - applying to routes...]
|
|
45
|
+
|
|
46
|
+
□ [VALIDATE]
|
|
47
|
+
- Guard: PASS (0 errors, 0 warnings)
|
|
48
|
+
- Tests: All passing
|
|
49
|
+
- Integration: Manual test with curl
|
|
50
|
+
|
|
51
|
+
✓ Final: guard PASS | 0 errors, 0 warnings
|
|
52
|
+
|
|
53
|
+
Development complete. Added rate limiting:
|
|
54
|
+
- Created: src/myapp/core/rate_limiter.py
|
|
55
|
+
- Modified: src/myapp/shell/api.py (decorator applied)
|
|
56
|
+
- Config: RATE_LIMIT_REQUESTS, RATE_LIMIT_WINDOW in .env"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Key Points
|
|
60
|
+
|
|
61
|
+
1. **Check-In first** - Shows project, branch, git status (no guard/map here)
|
|
62
|
+
2. **UNDERSTAND visible** - Task scope and constraints clear
|
|
63
|
+
3. **SPECIFY visible** - Contracts shown BEFORE implementation
|
|
64
|
+
4. **BUILD hidden** - Internal work, not in checkpoints
|
|
65
|
+
5. **VALIDATE visible** - Guard results and integration status
|
|
66
|
+
6. **Final last** - Runs guard and completes the session
|
|
67
|
+
|
|
68
|
+
## When to Use
|
|
69
|
+
|
|
70
|
+
| Complexity | Use Visible Workflow? |
|
|
71
|
+
|------------|----------------------|
|
|
72
|
+
| 3+ functions | Yes |
|
|
73
|
+
| Architectural changes | Yes |
|
|
74
|
+
| New Core module | Yes |
|
|
75
|
+
| Single-line fix | No |
|
|
76
|
+
| Documentation only | No |
|
|
77
|
+
| Trivial refactoring | No |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
*Example for the Invar Protocol v5.0*
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# DX-49: Template Manifest
|
|
2
|
+
# Defines ownership rules and template behaviors
|
|
3
|
+
|
|
4
|
+
[meta]
|
|
5
|
+
version = "5.0"
|
|
6
|
+
workflow = "USBV"
|
|
7
|
+
|
|
8
|
+
# =============================================================================
|
|
9
|
+
# Ownership Classification
|
|
10
|
+
# =============================================================================
|
|
11
|
+
|
|
12
|
+
[ownership]
|
|
13
|
+
# Fully managed - safe to overwrite completely
|
|
14
|
+
fully_managed = [
|
|
15
|
+
"INVAR.md",
|
|
16
|
+
".invar/examples/**",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
# Partially managed - only update marked regions
|
|
20
|
+
partially_managed = [
|
|
21
|
+
"CLAUDE.md",
|
|
22
|
+
".claude/skills/*/SKILL.md",
|
|
23
|
+
".claude/commands/*.md",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
# Never touch - other tools or user private files
|
|
27
|
+
never_touch = [
|
|
28
|
+
".claude/settings*.json",
|
|
29
|
+
".mcp.json",
|
|
30
|
+
".cursorrules",
|
|
31
|
+
".aider*",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# =============================================================================
|
|
35
|
+
# Region Definitions
|
|
36
|
+
# =============================================================================
|
|
37
|
+
|
|
38
|
+
[regions."CLAUDE.md"]
|
|
39
|
+
managed = { action = "overwrite" }
|
|
40
|
+
project = { action = "inject", source = ".invar/project-additions.md" }
|
|
41
|
+
user = { action = "preserve" }
|
|
42
|
+
|
|
43
|
+
[regions.".claude/skills/*/SKILL.md"]
|
|
44
|
+
skill = { action = "overwrite" }
|
|
45
|
+
extensions = { action = "preserve" }
|
|
46
|
+
|
|
47
|
+
# =============================================================================
|
|
48
|
+
# Template Generation
|
|
49
|
+
# =============================================================================
|
|
50
|
+
|
|
51
|
+
[templates]
|
|
52
|
+
# Protocol files (direct copy)
|
|
53
|
+
"INVAR.md" = { src = "protocol/INVAR.md", type = "copy" }
|
|
54
|
+
|
|
55
|
+
# Config files (Jinja2 templates)
|
|
56
|
+
"CLAUDE.md" = { src = "config/CLAUDE.md.jinja", type = "jinja" }
|
|
57
|
+
".invar/context.md" = { src = "config/context.md.jinja", type = "jinja" }
|
|
58
|
+
".pre-commit-config.yaml" = { src = "config/pre-commit.yaml.jinja", type = "jinja" }
|
|
59
|
+
|
|
60
|
+
# Skills (Jinja2 with syntax variable)
|
|
61
|
+
".claude/skills/develop/SKILL.md" = { src = "skills/develop/SKILL.md.jinja", type = "jinja" }
|
|
62
|
+
".claude/skills/investigate/SKILL.md" = { src = "skills/investigate/SKILL.md.jinja", type = "jinja" }
|
|
63
|
+
".claude/skills/propose/SKILL.md" = { src = "skills/propose/SKILL.md.jinja", type = "jinja" }
|
|
64
|
+
".claude/skills/review/SKILL.md" = { src = "skills/review/SKILL.md.jinja", type = "jinja" }
|
|
65
|
+
|
|
66
|
+
# Commands (direct copy)
|
|
67
|
+
".claude/commands/audit.md" = { src = "commands/audit.md", type = "copy" }
|
|
68
|
+
".claude/commands/guard.md" = { src = "commands/guard.md", type = "copy" }
|
|
69
|
+
|
|
70
|
+
# Examples (directory copy)
|
|
71
|
+
".invar/examples/" = { src = "examples/", type = "copy_dir" }
|
|
72
|
+
|
|
73
|
+
# =============================================================================
|
|
74
|
+
# Variables
|
|
75
|
+
# =============================================================================
|
|
76
|
+
|
|
77
|
+
[variables]
|
|
78
|
+
# Available for Jinja2 templates
|
|
79
|
+
syntax = "cli" # "cli" or "mcp"
|
|
80
|
+
version = "5.0"
|
|
81
|
+
project_name = "" # Set by init
|
|
82
|
+
|
|
83
|
+
# =============================================================================
|
|
84
|
+
# Command Behaviors
|
|
85
|
+
# =============================================================================
|
|
86
|
+
|
|
87
|
+
[commands.init]
|
|
88
|
+
# Files created by invar init
|
|
89
|
+
creates = [
|
|
90
|
+
"INVAR.md",
|
|
91
|
+
"CLAUDE.md",
|
|
92
|
+
".invar/context.md",
|
|
93
|
+
".invar/examples/",
|
|
94
|
+
".claude/skills/",
|
|
95
|
+
".claude/commands/",
|
|
96
|
+
".pre-commit-config.yaml",
|
|
97
|
+
]
|
|
98
|
+
syntax = "cli" # Default syntax for new projects
|
|
99
|
+
|
|
100
|
+
[commands.update]
|
|
101
|
+
# How invar update handles existing files
|
|
102
|
+
overwrite = ["INVAR.md", ".invar/examples/"]
|
|
103
|
+
merge = ["CLAUDE.md", ".claude/skills/"]
|
|
104
|
+
skip = [".invar/context.md"]
|
|
105
|
+
|
|
106
|
+
[commands.sync_self]
|
|
107
|
+
# For Invar project only
|
|
108
|
+
syntax = "mcp"
|
|
109
|
+
inject_project_additions = true
|
|
110
|
+
|
|
111
|
+
# =============================================================================
|
|
112
|
+
# DX-56: Sync Configuration (Unified Sync Engine)
|
|
113
|
+
# =============================================================================
|
|
114
|
+
|
|
115
|
+
[sync]
|
|
116
|
+
# Files that are fully managed (overwrite completely on sync)
|
|
117
|
+
fully_managed = [
|
|
118
|
+
"INVAR.md",
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
# Files with region-based updates (update managed, preserve user)
|
|
122
|
+
region_managed = [
|
|
123
|
+
"CLAUDE.md",
|
|
124
|
+
".claude/skills/develop/SKILL.md",
|
|
125
|
+
".claude/skills/investigate/SKILL.md",
|
|
126
|
+
".claude/skills/propose/SKILL.md",
|
|
127
|
+
".claude/skills/review/SKILL.md",
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
# Files created once, never updated by sync
|
|
131
|
+
create_only = [
|
|
132
|
+
".invar/context.md",
|
|
133
|
+
".invar/examples/",
|
|
134
|
+
".pre-commit-config.yaml",
|
|
135
|
+
".claude/commands/audit.md",
|
|
136
|
+
".claude/commands/guard.md",
|
|
137
|
+
]
|
|
@@ -0,0 +1,210 @@
|
|
|
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
|
+
License: CC-BY-4.0 (Creative Commons Attribution 4.0 International)
|
|
10
|
+
https://creativecommons.org/licenses/by/4.0/
|
|
11
|
+
|
|
12
|
+
You are free to share and adapt this document, provided you give
|
|
13
|
+
appropriate credit to the Invar project.
|
|
14
|
+
-->
|
|
15
|
+
# The Invar Protocol v5.0
|
|
16
|
+
|
|
17
|
+
> **"Trade structure for safety."**
|
|
18
|
+
|
|
19
|
+
## Six Laws
|
|
20
|
+
|
|
21
|
+
| Law | Principle |
|
|
22
|
+
|-----|-----------|
|
|
23
|
+
| 1. Separation | Core (pure logic) / Shell (I/O) physically separate |
|
|
24
|
+
| 2. Contract Complete | @pre/@post + doctests uniquely determine implementation |
|
|
25
|
+
| 3. Context Economy | map → sig → code (only read what's needed) |
|
|
26
|
+
| 4. Decompose First | Break into sub-functions before implementing |
|
|
27
|
+
| 5. Verify Reflectively | Fail → Reflect (why?) → Fix → Verify |
|
|
28
|
+
| 6. Integrate Fully | Local correct ≠ Global correct; verify all paths |
|
|
29
|
+
|
|
30
|
+
## Core/Shell Architecture
|
|
31
|
+
|
|
32
|
+
| Zone | Location | Requirements |
|
|
33
|
+
|------|----------|--------------|
|
|
34
|
+
| Core | `**/core/**` | @pre/@post, pure (no I/O), doctests |
|
|
35
|
+
| Shell | `**/shell/**` | `Result[T, E]` return type |
|
|
36
|
+
|
|
37
|
+
**Forbidden in Core:** `os`, `sys`, `subprocess`, `pathlib`, `open`, `requests`, `datetime.now`
|
|
38
|
+
|
|
39
|
+
## Core Example (Pure Logic)
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from deal import pre, post
|
|
43
|
+
|
|
44
|
+
@pre(lambda price, discount: price > 0 and 0 <= discount <= 1)
|
|
45
|
+
@post(lambda result: result >= 0)
|
|
46
|
+
def discounted_price(price: float, discount: float) -> float:
|
|
47
|
+
"""
|
|
48
|
+
>>> discounted_price(100, 0.2)
|
|
49
|
+
80.0
|
|
50
|
+
>>> discounted_price(100, 0) # Edge: no discount
|
|
51
|
+
100.0
|
|
52
|
+
"""
|
|
53
|
+
return price * (1 - discount)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Self-test:** Can someone else write the exact same function from just @pre/@post + doctests?
|
|
57
|
+
|
|
58
|
+
## Shell Example (I/O Operations)
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from pathlib import Path
|
|
62
|
+
from returns.result import Result, Success, Failure
|
|
63
|
+
|
|
64
|
+
def read_config(path: Path) -> Result[dict, str]:
|
|
65
|
+
"""Shell: handles I/O, returns Result for error handling."""
|
|
66
|
+
try:
|
|
67
|
+
import json
|
|
68
|
+
return Success(json.loads(path.read_text()))
|
|
69
|
+
except FileNotFoundError:
|
|
70
|
+
return Failure(f"File not found: {path}")
|
|
71
|
+
except json.JSONDecodeError as e:
|
|
72
|
+
return Failure(f"Invalid JSON: {e}")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Pattern:** Shell reads file → passes content to Core → returns Result.
|
|
76
|
+
|
|
77
|
+
More examples: `.invar/examples/`
|
|
78
|
+
|
|
79
|
+
## Check-In (Required)
|
|
80
|
+
|
|
81
|
+
Your first message MUST display:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
✓ Check-In: [project] | [branch] | [clean/dirty]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Actions:
|
|
88
|
+
1. Read `.invar/context.md` (Key Rules + Current State + Lessons Learned)
|
|
89
|
+
2. Show one-line status
|
|
90
|
+
|
|
91
|
+
**Do NOT execute guard or map at Check-In.**
|
|
92
|
+
Guard is for VALIDATE phase and Final only.
|
|
93
|
+
|
|
94
|
+
This is your sign-in. The user sees it immediately.
|
|
95
|
+
No visible check-in = Session not started.
|
|
96
|
+
|
|
97
|
+
## USBV Workflow (DX-32)
|
|
98
|
+
|
|
99
|
+
**U**nderstand → **S**pecify → **B**uild → **V**alidate
|
|
100
|
+
|
|
101
|
+
| Phase | Purpose | Activities |
|
|
102
|
+
|-------|---------|------------|
|
|
103
|
+
| UNDERSTAND | Know what and why | Intent, Inspect (invar sig/map), Constraints |
|
|
104
|
+
| SPECIFY | Define boundaries | @pre/@post, Design decomposition, Doctests |
|
|
105
|
+
| BUILD | Write code | Implement leaves, Compose |
|
|
106
|
+
| VALIDATE | Confirm correctness | invar guard, Review Gate, Reflect |
|
|
107
|
+
|
|
108
|
+
**Key:** Inspect before Contract. Depth varies naturally. Iterate when needed.
|
|
109
|
+
|
|
110
|
+
**Review Gate:** When Guard triggers `review_suggested` (escape hatches ≥3, security paths, low coverage), invoke `/review` before completion.
|
|
111
|
+
|
|
112
|
+
## Visible Workflow (DX-30)
|
|
113
|
+
|
|
114
|
+
For complex tasks (3+ functions), show 3 checkpoints in TodoList:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
□ [UNDERSTAND] Task description, codebase context, constraints
|
|
118
|
+
□ [SPECIFY] Contracts (@pre/@post) and design decomposition
|
|
119
|
+
□ [VALIDATE] Guard results, Review Gate if triggered, integration status
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**BUILD is internal work** — not shown in TodoList.
|
|
123
|
+
|
|
124
|
+
**Show contracts before code.** Example:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
[SPECIFY] calculate_discount:
|
|
128
|
+
@pre(lambda price, rate: price > 0 and 0 <= rate <= 1)
|
|
129
|
+
@post(lambda result: result >= 0)
|
|
130
|
+
def calculate_discount(price: float, rate: float) -> float: ...
|
|
131
|
+
|
|
132
|
+
[BUILD] Now coding...
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**When to use:** New features (3+ functions), architectural changes, Core modifications.
|
|
136
|
+
**Skip for:** Single-line fixes, documentation, trivial refactoring.
|
|
137
|
+
|
|
138
|
+
## Task Completion
|
|
139
|
+
|
|
140
|
+
A task is complete only when ALL conditions are met:
|
|
141
|
+
- Check-In displayed: `✓ Check-In: [project] | [branch] | [clean/dirty]`
|
|
142
|
+
- Intent explicitly stated
|
|
143
|
+
- Contract written before implementation
|
|
144
|
+
- Final displayed: `✓ Final: guard PASS | <errors>, <warnings>`
|
|
145
|
+
- User requirement satisfied
|
|
146
|
+
|
|
147
|
+
**Missing any = Task incomplete.**
|
|
148
|
+
|
|
149
|
+
## Markers
|
|
150
|
+
|
|
151
|
+
### Entry Points
|
|
152
|
+
|
|
153
|
+
Entry points are framework callbacks (`@app.route`, `@app.command`) at Shell boundary.
|
|
154
|
+
- **Exempt** from `Result[T, E]` — must match framework signature
|
|
155
|
+
- **Keep thin** (max 15 lines) — delegate to Shell functions that return Result
|
|
156
|
+
|
|
157
|
+
Auto-detected by decorators. For custom callbacks:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
# @shell:entry
|
|
161
|
+
def on_custom_event(data: dict) -> dict:
|
|
162
|
+
result = handle_event(data)
|
|
163
|
+
return result.unwrap_or({"error": "failed"})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Shell Complexity
|
|
167
|
+
|
|
168
|
+
When shell function complexity is justified:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
# @shell_complexity: Subprocess with error classification
|
|
172
|
+
def run_external_tool(...): ...
|
|
173
|
+
|
|
174
|
+
# @shell_orchestration: Multi-step pipeline coordination
|
|
175
|
+
def process_batch(...): ...
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Architecture Escape Hatch
|
|
179
|
+
|
|
180
|
+
When rule violation has valid architectural justification:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
# @invar:allow shell_result: Framework callback signature fixed
|
|
184
|
+
def flask_handler(): ...
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
See `invar rules` for all rule names.
|
|
188
|
+
|
|
189
|
+
## Commands
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
invar guard # Full: static + doctests + CrossHair + Hypothesis (default)
|
|
193
|
+
invar guard --static # Static only (quick debug, ~0.5s)
|
|
194
|
+
invar guard --changed # Modified files only
|
|
195
|
+
invar sig <file> # Show contracts + signatures
|
|
196
|
+
invar map --top 10 # Most-referenced symbols
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Configuration
|
|
200
|
+
|
|
201
|
+
```toml
|
|
202
|
+
[tool.invar.guard]
|
|
203
|
+
core_paths = ["src/myapp/core"]
|
|
204
|
+
shell_paths = ["src/myapp/shell"]
|
|
205
|
+
# DX-22: Doctest lines are always excluded from size calculations by default
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
*Protocol v5.0 — USBV workflow (DX-32) | [Guide](docs/guide.md) | [Examples](.invar/examples/)*
|