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
@@ -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 Session Start, ICIDIV workflow, and Task Completion requirements.
3
+ Follow the Invar Protocol in INVAR.md — includes Check-In, USBV workflow, and Task Completion requirements.
4
4
 
5
- ## Cursor-Specific: Entry Verification
5
+ ## Check-In
6
6
 
7
- Before writing any code, execute and show output from:
7
+ Your first message MUST display:
8
8
 
9
9
  ```
10
- invar_guard (changed=true) # Check existing violations
11
- invar_map (top=10) # Understand code structure
10
+ Check-In: [project] | [branch] | [clean/dirty]
12
11
  ```
13
12
 
14
- Use MCP tools if available, otherwise use CLI commands:
15
- - `invar guard --changed`
16
- - `invar map --top 10`
13
+ Actions:
14
+ 1. Read `.invar/context.md` (Key Rules + Current State + Lessons Learned)
15
+ 2. Show one-line status
17
16
 
18
- Then read .invar/examples/ and .invar/context.md.
17
+ **Do NOT execute guard or map at Check-In.**
18
+ Guard is for VALIDATE phase and Final only.
19
19
 
20
- Skipping these steps leads to non-compliant code.
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: IntentContractInspectDesign → Implement → Verify
27
- - Contract before Implement. No exceptions.
28
- - Task complete only when final invar_guard passes.
39
+ - Workflow: UnderstandSpecifyBuildValidate (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
 
@@ -0,0 +1,3 @@
1
+ # Skip pytest collection for example files
2
+ # These are reference examples, not tests.
3
+ collect_ignore = ["contracts.py", "core_shell.py", "workflow.md"]
@@ -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: isinstance(data, dict))
58
- @post(lambda result: isinstance(result, dict))
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
- @pre(lambda content: isinstance(content, str))
22
- @post(lambda result: isinstance(result, list))
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
- @pre(lambda items: isinstance(items, list))
38
- @post(lambda result: isinstance(result, dict))
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/)*