invar-tools 1.8.0__py3-none-any.whl → 1.11.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 +8 -0
- invar/core/doc_edit.py +187 -0
- invar/core/doc_parser.py +563 -0
- invar/core/language.py +88 -0
- invar/core/models.py +106 -0
- invar/core/patterns/detector.py +6 -1
- invar/core/patterns/p0_exhaustive.py +15 -3
- invar/core/patterns/p0_literal.py +15 -3
- invar/core/patterns/p0_newtype.py +15 -3
- invar/core/patterns/p0_nonempty.py +15 -3
- invar/core/patterns/p0_validation.py +15 -3
- invar/core/patterns/registry.py +5 -1
- invar/core/patterns/types.py +5 -1
- invar/core/property_gen.py +4 -0
- invar/core/rules.py +84 -18
- invar/core/sync_helpers.py +27 -1
- invar/core/ts_parsers.py +286 -0
- invar/core/ts_sig_parser.py +310 -0
- invar/mcp/handlers.py +408 -0
- invar/mcp/server.py +288 -143
- invar/node_tools/MANIFEST +7 -0
- invar/node_tools/__init__.py +51 -0
- invar/node_tools/fc-runner/cli.js +77 -0
- invar/node_tools/quick-check/cli.js +28 -0
- invar/node_tools/ts-analyzer/cli.js +480 -0
- invar/shell/claude_hooks.py +35 -12
- invar/shell/commands/doc.py +409 -0
- invar/shell/commands/guard.py +41 -1
- invar/shell/commands/init.py +154 -16
- invar/shell/commands/perception.py +157 -33
- invar/shell/commands/skill.py +187 -0
- invar/shell/commands/template_sync.py +65 -13
- invar/shell/commands/uninstall.py +60 -12
- invar/shell/commands/update.py +6 -14
- invar/shell/contract_coverage.py +1 -0
- invar/shell/doc_tools.py +459 -0
- invar/shell/fs.py +67 -13
- invar/shell/pi_hooks.py +6 -0
- invar/shell/prove/crosshair.py +3 -0
- invar/shell/prove/guard_ts.py +902 -0
- invar/shell/skill_manager.py +355 -0
- invar/shell/template_engine.py +28 -4
- invar/shell/templates.py +4 -4
- invar/templates/claude-md/python/critical-rules.md +33 -0
- invar/templates/claude-md/python/quick-reference.md +24 -0
- invar/templates/claude-md/typescript/critical-rules.md +40 -0
- invar/templates/claude-md/typescript/quick-reference.md +24 -0
- invar/templates/claude-md/universal/check-in.md +25 -0
- invar/templates/claude-md/universal/skills.md +73 -0
- invar/templates/claude-md/universal/workflow.md +55 -0
- invar/templates/commands/{audit.md → audit.md.jinja} +18 -1
- invar/templates/config/AGENT.md.jinja +58 -0
- invar/templates/config/CLAUDE.md.jinja +16 -209
- invar/templates/config/context.md.jinja +19 -0
- invar/templates/examples/{README.md → python/README.md} +2 -0
- invar/templates/examples/{conftest.py → python/conftest.py} +1 -1
- invar/templates/examples/{contracts.py → python/contracts.py} +81 -4
- invar/templates/examples/python/core_shell.py +227 -0
- invar/templates/examples/python/functional.py +613 -0
- invar/templates/examples/typescript/README.md +31 -0
- invar/templates/examples/typescript/contracts.ts +163 -0
- invar/templates/examples/typescript/core_shell.ts +374 -0
- invar/templates/examples/typescript/functional.ts +601 -0
- invar/templates/examples/typescript/workflow.md +95 -0
- invar/templates/hooks/PostToolUse.sh.jinja +10 -1
- invar/templates/hooks/PreToolUse.sh.jinja +38 -0
- invar/templates/hooks/Stop.sh.jinja +1 -1
- invar/templates/hooks/UserPromptSubmit.sh.jinja +7 -0
- invar/templates/hooks/pi/invar.ts.jinja +9 -0
- invar/templates/manifest.toml +7 -6
- invar/templates/onboard/assessment.md.jinja +214 -0
- invar/templates/onboard/patterns/python.md +347 -0
- invar/templates/onboard/patterns/typescript.md +452 -0
- invar/templates/onboard/roadmap.md.jinja +168 -0
- invar/templates/protocol/INVAR.md.jinja +51 -0
- invar/templates/protocol/python/architecture-examples.md +41 -0
- invar/templates/protocol/python/contracts-syntax.md +56 -0
- invar/templates/protocol/python/markers.md +44 -0
- invar/templates/protocol/python/tools.md +24 -0
- invar/templates/protocol/python/troubleshooting.md +38 -0
- invar/templates/protocol/typescript/architecture-examples.md +52 -0
- invar/templates/protocol/typescript/contracts-syntax.md +73 -0
- invar/templates/protocol/typescript/markers.md +48 -0
- invar/templates/protocol/typescript/tools.md +65 -0
- invar/templates/protocol/typescript/troubleshooting.md +104 -0
- invar/templates/protocol/universal/architecture.md +36 -0
- invar/templates/protocol/universal/completion.md +14 -0
- invar/templates/protocol/universal/contracts-concept.md +37 -0
- invar/templates/protocol/universal/header.md +17 -0
- invar/templates/protocol/universal/session.md +17 -0
- invar/templates/protocol/universal/six-laws.md +10 -0
- invar/templates/protocol/universal/usbv.md +14 -0
- invar/templates/protocol/universal/visible-workflow.md +25 -0
- invar/templates/skills/develop/SKILL.md.jinja +85 -3
- invar/templates/skills/extensions/_registry.yaml +93 -0
- invar/templates/skills/extensions/acceptance/SKILL.md +383 -0
- invar/templates/skills/extensions/invar-onboard/SKILL.md +448 -0
- invar/templates/skills/extensions/invar-onboard/patterns/python.md +347 -0
- invar/templates/skills/extensions/invar-onboard/patterns/typescript.md +452 -0
- invar/templates/skills/extensions/invar-onboard/templates/assessment.md.jinja +214 -0
- invar/templates/skills/extensions/invar-onboard/templates/roadmap.md.jinja +168 -0
- invar/templates/skills/extensions/security/SKILL.md +382 -0
- invar/templates/skills/extensions/security/patterns/_common.yaml +126 -0
- invar/templates/skills/extensions/security/patterns/python.yaml +155 -0
- invar/templates/skills/extensions/security/patterns/typescript.yaml +194 -0
- invar/templates/skills/review/SKILL.md.jinja +220 -248
- {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/METADATA +336 -12
- invar_tools-1.11.0.dist-info/RECORD +178 -0
- invar/templates/examples/core_shell.py +0 -127
- invar/templates/protocol/INVAR.md +0 -310
- invar_tools-1.8.0.dist-info/RECORD +0 -116
- /invar/templates/examples/{workflow.md → python/workflow.md} +0 -0
- {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/WHEEL +0 -0
- {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/entry_points.txt +0 -0
- {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/licenses/LICENSE +0 -0
- {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/licenses/LICENSE-GPL +0 -0
- {invar_tools-1.8.0.dist-info → invar_tools-1.11.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# ruff: noqa: ERA001
|
|
2
|
+
"""
|
|
3
|
+
Invar Core/Shell Separation Examples
|
|
4
|
+
|
|
5
|
+
Reference patterns for Core vs Shell architecture.
|
|
6
|
+
Managed by Invar - do not edit directly.
|
|
7
|
+
"""
|
|
8
|
+
# @invar:allow forbidden_import: Example file demonstrates Shell pattern with pathlib
|
|
9
|
+
# @invar:allow missing_contract: Shell functions intentionally without contracts to show pattern
|
|
10
|
+
# @invar:allow contract_quality_ratio: Educational file with Shell examples
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# invar_runtime supports both lambda and Contract objects
|
|
15
|
+
from invar_runtime import post, pre
|
|
16
|
+
from returns.result import Failure, Result, Success
|
|
17
|
+
|
|
18
|
+
# =============================================================================
|
|
19
|
+
# CORE: Pure Logic (no I/O)
|
|
20
|
+
# =============================================================================
|
|
21
|
+
# Location: src/*/core/
|
|
22
|
+
# Requirements: @pre/@post, doctests, no I/O imports
|
|
23
|
+
# =============================================================================
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# @invar:allow shell_result: Example file - demonstrates Core pattern
|
|
27
|
+
# @shell_orchestration: Example file - demonstrates Core pattern
|
|
28
|
+
@pre(lambda content: content is not None) # Accepts any string including empty
|
|
29
|
+
@post(lambda result: all(line.strip() == line and line for line in result)) # No whitespace, non-empty
|
|
30
|
+
def parse_lines(content: str) -> list[str]:
|
|
31
|
+
"""
|
|
32
|
+
Parse content into non-empty lines.
|
|
33
|
+
|
|
34
|
+
>>> parse_lines("a\\nb\\nc")
|
|
35
|
+
['a', 'b', 'c']
|
|
36
|
+
>>> parse_lines("")
|
|
37
|
+
[]
|
|
38
|
+
>>> parse_lines(" \\n ") # Edge: whitespace only
|
|
39
|
+
[]
|
|
40
|
+
"""
|
|
41
|
+
return [line.strip() for line in content.split("\n") if line.strip()]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# @invar:allow shell_result: Example file - demonstrates Core pattern
|
|
45
|
+
# @shell_orchestration: Example file - demonstrates Core pattern
|
|
46
|
+
@pre(lambda items: all(isinstance(i, str) for i in items)) # All items must be strings
|
|
47
|
+
@post(lambda result: all(v > 0 for v in result.values())) # All counts are positive
|
|
48
|
+
def count_items(items: list[str]) -> dict[str, int]:
|
|
49
|
+
"""
|
|
50
|
+
Count occurrences of each item.
|
|
51
|
+
|
|
52
|
+
>>> sorted(count_items(['a', 'b', 'a']).items())
|
|
53
|
+
[('a', 2), ('b', 1)]
|
|
54
|
+
>>> count_items([])
|
|
55
|
+
{}
|
|
56
|
+
"""
|
|
57
|
+
counts: dict[str, int] = {}
|
|
58
|
+
for item in items:
|
|
59
|
+
counts[item] = counts.get(item, 0) + 1
|
|
60
|
+
return counts
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# =============================================================================
|
|
64
|
+
# SHELL: I/O Operations
|
|
65
|
+
# =============================================================================
|
|
66
|
+
# Location: src/*/shell/
|
|
67
|
+
# Requirements: Result[T, E] return type, calls Core for logic
|
|
68
|
+
# =============================================================================
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def read_file(path: Path) -> Result[str, str]:
|
|
72
|
+
"""
|
|
73
|
+
Read file content.
|
|
74
|
+
|
|
75
|
+
Shell handles I/O, returns Result for error handling.
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
return Success(path.read_text())
|
|
79
|
+
except FileNotFoundError:
|
|
80
|
+
return Failure(f"File not found: {path}")
|
|
81
|
+
except PermissionError:
|
|
82
|
+
return Failure(f"Permission denied: {path}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def count_lines_in_file(path: Path) -> Result[dict[str, int], str]:
|
|
86
|
+
"""
|
|
87
|
+
Count lines in file - demonstrates Core/Shell integration.
|
|
88
|
+
|
|
89
|
+
Shell reads file → Core parses content → Shell returns result.
|
|
90
|
+
"""
|
|
91
|
+
# Shell: I/O operation
|
|
92
|
+
content_result = read_file(path)
|
|
93
|
+
|
|
94
|
+
if isinstance(content_result, Failure):
|
|
95
|
+
return content_result
|
|
96
|
+
|
|
97
|
+
content = content_result.unwrap()
|
|
98
|
+
|
|
99
|
+
# Core: Pure logic (no I/O)
|
|
100
|
+
lines = parse_lines(content)
|
|
101
|
+
counts = count_items(lines)
|
|
102
|
+
|
|
103
|
+
# Shell: Return result
|
|
104
|
+
return Success(counts)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# =============================================================================
|
|
108
|
+
# ANTI-PATTERNS
|
|
109
|
+
# =============================================================================
|
|
110
|
+
|
|
111
|
+
# DON'T: I/O in Core
|
|
112
|
+
# def parse_file(path: Path): # BAD: Path in Core
|
|
113
|
+
# content = path.read_text() # BAD: I/O in Core
|
|
114
|
+
# return parse_lines(content)
|
|
115
|
+
|
|
116
|
+
# DO: Core receives content, not paths
|
|
117
|
+
# def parse_content(content: str): # GOOD: receives data
|
|
118
|
+
# return parse_lines(content)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# DON'T: Missing Result in Shell
|
|
122
|
+
# def load_config(path: Path) -> dict: # BAD: no Result type
|
|
123
|
+
# return json.loads(path.read_text()) # Exceptions not handled
|
|
124
|
+
|
|
125
|
+
# DO: Return Result[T, E]
|
|
126
|
+
# def load_config(path: Path) -> Result[dict, str]: # GOOD
|
|
127
|
+
# try:
|
|
128
|
+
# return Success(json.loads(path.read_text()))
|
|
129
|
+
# except Exception as e:
|
|
130
|
+
# return Failure(str(e))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# =============================================================================
|
|
134
|
+
# FastAPI Integration Pattern
|
|
135
|
+
# =============================================================================
|
|
136
|
+
# Demonstrates how to use Result with FastAPI endpoints.
|
|
137
|
+
# Shell (API handler) → Core (business logic) → Shell (HTTP response)
|
|
138
|
+
|
|
139
|
+
# NOTE: This is pseudocode - requires FastAPI to be installed
|
|
140
|
+
# from fastapi import FastAPI, HTTPException
|
|
141
|
+
# from pydantic import BaseModel
|
|
142
|
+
|
|
143
|
+
# class UserResponse(BaseModel):
|
|
144
|
+
# id: str
|
|
145
|
+
# name: str
|
|
146
|
+
|
|
147
|
+
# app = FastAPI()
|
|
148
|
+
|
|
149
|
+
# CORE: Pure business logic
|
|
150
|
+
# @pre(lambda user_id: len(user_id) > 0)
|
|
151
|
+
# @post(lambda result: result.get("name") is not None)
|
|
152
|
+
# def get_user_data(user_id: str) -> dict:
|
|
153
|
+
# """Core function - pure logic, receives string ID."""
|
|
154
|
+
# # Business logic here (no HTTP, no database I/O)
|
|
155
|
+
# return {"id": user_id, "name": "Demo User"}
|
|
156
|
+
|
|
157
|
+
# SHELL: I/O layer - database
|
|
158
|
+
# def fetch_user_from_db(user_id: str) -> Result[dict, str]:
|
|
159
|
+
# """Shell function - database I/O, returns Result."""
|
|
160
|
+
# try:
|
|
161
|
+
# # In real code: query database
|
|
162
|
+
# if user_id == "not-found":
|
|
163
|
+
# return Failure("User not found")
|
|
164
|
+
# return Success({"id": user_id, "name": "DB User"})
|
|
165
|
+
# except Exception as e:
|
|
166
|
+
# return Failure(f"Database error: {e}")
|
|
167
|
+
|
|
168
|
+
# SHELL: I/O layer - HTTP endpoint
|
|
169
|
+
# @app.get("/users/{user_id}", response_model=UserResponse)
|
|
170
|
+
# def get_user_endpoint(user_id: str):
|
|
171
|
+
# """
|
|
172
|
+
# FastAPI endpoint - Shell layer.
|
|
173
|
+
#
|
|
174
|
+
# Pattern: Shell → Core → Shell
|
|
175
|
+
# 1. Shell receives HTTP request
|
|
176
|
+
# 2. Core processes business logic
|
|
177
|
+
# 3. Shell converts Result to HTTP response
|
|
178
|
+
# """
|
|
179
|
+
# result = fetch_user_from_db(user_id)
|
|
180
|
+
#
|
|
181
|
+
# # Convert Result to HTTP response
|
|
182
|
+
# match result:
|
|
183
|
+
# case Success(user):
|
|
184
|
+
# return UserResponse(**user)
|
|
185
|
+
# case Failure(error):
|
|
186
|
+
# if "not found" in error.lower():
|
|
187
|
+
# raise HTTPException(status_code=404, detail=error)
|
|
188
|
+
# raise HTTPException(status_code=500, detail=error)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# =============================================================================
|
|
192
|
+
# Result → HTTP Response Mapping
|
|
193
|
+
# =============================================================================
|
|
194
|
+
# Common pattern for converting Result errors to HTTP status codes:
|
|
195
|
+
#
|
|
196
|
+
# | Error Type | HTTP Status | When to Use |
|
|
197
|
+
# |-----------------|-------------|--------------------------------|
|
|
198
|
+
# | NotFoundError | 404 | Resource doesn't exist |
|
|
199
|
+
# | ValidationError | 400 | Invalid input from client |
|
|
200
|
+
# | AuthError | 401/403 | Authentication/authorization |
|
|
201
|
+
# | ConflictError | 409 | Resource state conflict |
|
|
202
|
+
# | InternalError | 500 | Unexpected server error |
|
|
203
|
+
#
|
|
204
|
+
# Example error types:
|
|
205
|
+
# @dataclass(frozen=True)
|
|
206
|
+
# class NotFoundError:
|
|
207
|
+
# resource: str
|
|
208
|
+
# id: str
|
|
209
|
+
#
|
|
210
|
+
# @dataclass(frozen=True)
|
|
211
|
+
# class ValidationError:
|
|
212
|
+
# field: str
|
|
213
|
+
# message: str
|
|
214
|
+
#
|
|
215
|
+
# AppError = NotFoundError | ValidationError | str
|
|
216
|
+
#
|
|
217
|
+
# def result_to_response(result: Result[T, AppError]) -> T:
|
|
218
|
+
# """Convert Result to HTTP response or raise appropriate HTTPException."""
|
|
219
|
+
# match result:
|
|
220
|
+
# case Success(value):
|
|
221
|
+
# return value
|
|
222
|
+
# case Failure(NotFoundError(resource, id)):
|
|
223
|
+
# raise HTTPException(404, f"{resource} {id} not found")
|
|
224
|
+
# case Failure(ValidationError(field, message)):
|
|
225
|
+
# raise HTTPException(400, f"Invalid {field}: {message}")
|
|
226
|
+
# case Failure(error):
|
|
227
|
+
# raise HTTPException(500, str(error))
|