invar-tools 1.8.0__py3-none-any.whl → 1.10.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 (110) hide show
  1. invar/__init__.py +8 -0
  2. invar/core/language.py +88 -0
  3. invar/core/models.py +106 -0
  4. invar/core/patterns/detector.py +6 -1
  5. invar/core/patterns/p0_exhaustive.py +15 -3
  6. invar/core/patterns/p0_literal.py +15 -3
  7. invar/core/patterns/p0_newtype.py +15 -3
  8. invar/core/patterns/p0_nonempty.py +15 -3
  9. invar/core/patterns/p0_validation.py +15 -3
  10. invar/core/patterns/registry.py +5 -1
  11. invar/core/patterns/types.py +5 -1
  12. invar/core/property_gen.py +4 -0
  13. invar/core/rules.py +84 -18
  14. invar/core/sync_helpers.py +27 -1
  15. invar/core/ts_parsers.py +286 -0
  16. invar/core/ts_sig_parser.py +307 -0
  17. invar/node_tools/MANIFEST +7 -0
  18. invar/node_tools/__init__.py +51 -0
  19. invar/node_tools/fc-runner/cli.js +77 -0
  20. invar/node_tools/quick-check/cli.js +28 -0
  21. invar/node_tools/ts-analyzer/cli.js +480 -0
  22. invar/shell/claude_hooks.py +35 -12
  23. invar/shell/commands/guard.py +36 -1
  24. invar/shell/commands/init.py +82 -3
  25. invar/shell/commands/perception.py +157 -33
  26. invar/shell/commands/skill.py +187 -0
  27. invar/shell/commands/template_sync.py +65 -13
  28. invar/shell/commands/uninstall.py +60 -12
  29. invar/shell/commands/update.py +6 -14
  30. invar/shell/contract_coverage.py +1 -0
  31. invar/shell/fs.py +66 -13
  32. invar/shell/pi_hooks.py +6 -0
  33. invar/shell/prove/guard_ts.py +899 -0
  34. invar/shell/skill_manager.py +353 -0
  35. invar/shell/template_engine.py +28 -4
  36. invar/shell/templates.py +4 -4
  37. invar/templates/claude-md/python/critical-rules.md +33 -0
  38. invar/templates/claude-md/python/quick-reference.md +24 -0
  39. invar/templates/claude-md/typescript/critical-rules.md +40 -0
  40. invar/templates/claude-md/typescript/quick-reference.md +24 -0
  41. invar/templates/claude-md/universal/check-in.md +25 -0
  42. invar/templates/claude-md/universal/skills.md +73 -0
  43. invar/templates/claude-md/universal/workflow.md +55 -0
  44. invar/templates/commands/{audit.md → audit.md.jinja} +18 -1
  45. invar/templates/config/AGENT.md.jinja +58 -0
  46. invar/templates/config/CLAUDE.md.jinja +16 -209
  47. invar/templates/config/context.md.jinja +19 -0
  48. invar/templates/examples/{README.md → python/README.md} +2 -0
  49. invar/templates/examples/{conftest.py → python/conftest.py} +1 -1
  50. invar/templates/examples/{contracts.py → python/contracts.py} +81 -4
  51. invar/templates/examples/python/core_shell.py +227 -0
  52. invar/templates/examples/python/functional.py +613 -0
  53. invar/templates/examples/typescript/README.md +31 -0
  54. invar/templates/examples/typescript/contracts.ts +163 -0
  55. invar/templates/examples/typescript/core_shell.ts +374 -0
  56. invar/templates/examples/typescript/functional.ts +601 -0
  57. invar/templates/examples/typescript/workflow.md +95 -0
  58. invar/templates/hooks/PostToolUse.sh.jinja +10 -1
  59. invar/templates/hooks/PreToolUse.sh.jinja +38 -0
  60. invar/templates/hooks/Stop.sh.jinja +1 -1
  61. invar/templates/hooks/UserPromptSubmit.sh.jinja +7 -0
  62. invar/templates/hooks/pi/invar.ts.jinja +9 -0
  63. invar/templates/manifest.toml +7 -6
  64. invar/templates/onboard/assessment.md.jinja +214 -0
  65. invar/templates/onboard/patterns/python.md +347 -0
  66. invar/templates/onboard/patterns/typescript.md +452 -0
  67. invar/templates/onboard/roadmap.md.jinja +168 -0
  68. invar/templates/protocol/INVAR.md.jinja +51 -0
  69. invar/templates/protocol/python/architecture-examples.md +41 -0
  70. invar/templates/protocol/python/contracts-syntax.md +56 -0
  71. invar/templates/protocol/python/markers.md +44 -0
  72. invar/templates/protocol/python/tools.md +24 -0
  73. invar/templates/protocol/python/troubleshooting.md +38 -0
  74. invar/templates/protocol/typescript/architecture-examples.md +52 -0
  75. invar/templates/protocol/typescript/contracts-syntax.md +73 -0
  76. invar/templates/protocol/typescript/markers.md +48 -0
  77. invar/templates/protocol/typescript/tools.md +65 -0
  78. invar/templates/protocol/typescript/troubleshooting.md +104 -0
  79. invar/templates/protocol/universal/architecture.md +36 -0
  80. invar/templates/protocol/universal/completion.md +14 -0
  81. invar/templates/protocol/universal/contracts-concept.md +37 -0
  82. invar/templates/protocol/universal/header.md +17 -0
  83. invar/templates/protocol/universal/session.md +17 -0
  84. invar/templates/protocol/universal/six-laws.md +10 -0
  85. invar/templates/protocol/universal/usbv.md +14 -0
  86. invar/templates/protocol/universal/visible-workflow.md +25 -0
  87. invar/templates/skills/develop/SKILL.md.jinja +39 -3
  88. invar/templates/skills/extensions/_registry.yaml +93 -0
  89. invar/templates/skills/extensions/acceptance/SKILL.md +383 -0
  90. invar/templates/skills/extensions/invar-onboard/SKILL.md +448 -0
  91. invar/templates/skills/extensions/invar-onboard/patterns/python.md +347 -0
  92. invar/templates/skills/extensions/invar-onboard/patterns/typescript.md +452 -0
  93. invar/templates/skills/extensions/invar-onboard/templates/assessment.md.jinja +214 -0
  94. invar/templates/skills/extensions/invar-onboard/templates/roadmap.md.jinja +168 -0
  95. invar/templates/skills/extensions/security/SKILL.md +382 -0
  96. invar/templates/skills/extensions/security/patterns/_common.yaml +126 -0
  97. invar/templates/skills/extensions/security/patterns/python.yaml +155 -0
  98. invar/templates/skills/extensions/security/patterns/typescript.yaml +194 -0
  99. invar/templates/skills/review/SKILL.md.jinja +331 -71
  100. {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/METADATA +304 -12
  101. invar_tools-1.10.0.dist-info/RECORD +173 -0
  102. invar/templates/examples/core_shell.py +0 -127
  103. invar/templates/protocol/INVAR.md +0 -310
  104. invar_tools-1.8.0.dist-info/RECORD +0 -116
  105. /invar/templates/examples/{workflow.md → python/workflow.md} +0 -0
  106. {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/WHEEL +0 -0
  107. {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/entry_points.txt +0 -0
  108. {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/licenses/LICENSE +0 -0
  109. {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/licenses/LICENSE-GPL +0 -0
  110. {invar_tools-1.8.0.dist-info → invar_tools-1.10.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,347 @@
1
+ # Python Onboarding Patterns
2
+
3
+ > Patterns for migrating Python projects to Invar framework.
4
+ > Library: `returns` (dry-python)
5
+
6
+ ## 1. Overview
7
+
8
+ ```python
9
+ # Library: returns (dry-python)
10
+ # Install: pip install returns
11
+
12
+ from returns.result import Result, Success, Failure
13
+ from returns.io import IOResultE
14
+ from returns.future import FutureResultE
15
+ ```
16
+
17
+ ---
18
+
19
+ ## 2. Error Handling
20
+
21
+ ### 2.1 Basic Transformation
22
+
23
+ ```python
24
+ # Before: raise
25
+ def get_user(id: str) -> User:
26
+ user = db.find(id)
27
+ if not user:
28
+ raise NotFoundError(f"User {id} not found")
29
+ return user
30
+
31
+ # After: Result
32
+ from returns.result import Result, Success, Failure
33
+
34
+ def get_user(id: str) -> Result[User, NotFoundError]:
35
+ user = db.find(id)
36
+ if not user:
37
+ return Failure(NotFoundError(f"User {id} not found"))
38
+ return Success(user)
39
+ ```
40
+
41
+ ### 2.2 Async Handling
42
+
43
+ ```python
44
+ from returns.future import FutureResultE
45
+ from returns.io import IOResultE
46
+
47
+ # Async I/O operations
48
+ async def get_user_async(id: str) -> FutureResultE[User, GetUserError]:
49
+ """Use FutureResultE for async operations."""
50
+ ...
51
+
52
+ # Sync I/O operations
53
+ def read_config() -> IOResultE[Config, ConfigError]:
54
+ """Use IOResultE for sync I/O with Result."""
55
+ ...
56
+ ```
57
+
58
+ ### 2.3 Exception Catching (@safe)
59
+
60
+ ```python
61
+ from returns.result import safe
62
+
63
+ @safe
64
+ def parse_json(data: str) -> dict:
65
+ """Automatically converts exceptions to Failure.
66
+
67
+ >>> parse_json('{"a": 1}').unwrap()
68
+ {'a': 1}
69
+ >>> parse_json('invalid').is_failure()
70
+ True
71
+ """
72
+ return json.loads(data) # JSONDecodeError -> Failure
73
+ ```
74
+
75
+ ### 2.4 Chaining (bind, map)
76
+
77
+ ```python
78
+ def process_order(order_id: str) -> Result[Receipt, OrderError]:
79
+ """Chain multiple Result operations."""
80
+ return (
81
+ get_order(order_id) # Result[Order, NotFoundError]
82
+ .bind(validate_order) # -> Result[Order, ValidationError]
83
+ .map(calculate_total) # -> Result[float, ...] (pure transform)
84
+ .bind(charge_payment) # -> Result[Payment, PaymentError]
85
+ .map(generate_receipt) # -> Result[Receipt, ...]
86
+ )
87
+ ```
88
+
89
+ ### 2.5 Error Type Hierarchy
90
+
91
+ ```python
92
+ from dataclasses import dataclass
93
+ from typing import Union
94
+ from returns.result import Result
95
+
96
+ @dataclass
97
+ class DomainError:
98
+ """Base error type."""
99
+ message: str
100
+ code: str
101
+
102
+ @dataclass
103
+ class NotFoundError(DomainError):
104
+ entity: str
105
+ id: str
106
+
107
+ @dataclass
108
+ class ValidationError(DomainError):
109
+ field: str
110
+ reason: str
111
+
112
+ # Union type for function signatures
113
+ OrderError = Union[NotFoundError, ValidationError, PaymentError]
114
+
115
+ def process(id: str) -> Result[Order, OrderError]:
116
+ ...
117
+ ```
118
+
119
+ ### 2.6 Combining Multiple Results
120
+
121
+ ```python
122
+ from returns.result import Result, Success
123
+ from returns.pointfree import bind
124
+ from returns.pipeline import flow
125
+
126
+ def validate_all(items: list[Item]) -> Result[list[Item], ValidationError]:
127
+ """Collect all validation results."""
128
+ results = [validate_item(item) for item in items]
129
+
130
+ # Fail on first error
131
+ for r in results:
132
+ if r.is_failure():
133
+ return r
134
+
135
+ return Success([r.unwrap() for r in results])
136
+ ```
137
+
138
+ ---
139
+
140
+ ## 3. Contracts
141
+
142
+ ### 3.1 Input Validation (@pre)
143
+
144
+ ```python
145
+ from deal import pre
146
+ from returns.result import Result
147
+
148
+ @pre(lambda id: len(id) > 0 and len(id) <= 36)
149
+ @pre(lambda id: id.replace("-", "").isalnum()) # UUID format
150
+ def get_user(id: str) -> Result[User, NotFoundError]:
151
+ """
152
+ Get user by ID.
153
+
154
+ >>> get_user("user-123").unwrap().name
155
+ 'Alice'
156
+ >>> get_user("").is_failure() # Precondition fails
157
+ True
158
+ """
159
+ ...
160
+ ```
161
+
162
+ ### 3.2 Pure Function Contracts
163
+
164
+ ```python
165
+ from deal import pre, post
166
+
167
+ @pre(lambda amount, rate=0.1: amount > 0 and 0 <= rate <= 1)
168
+ @post(lambda result: result >= 0)
169
+ def calculate_tax(amount: float, rate: float = 0.1) -> float:
170
+ """
171
+ Calculate tax amount.
172
+
173
+ >>> calculate_tax(100)
174
+ 10.0
175
+ >>> calculate_tax(100, 0.2)
176
+ 20.0
177
+ >>> calculate_tax(-100) # Precondition fails
178
+ Traceback (most recent call last):
179
+ ...
180
+ """
181
+ return amount * rate
182
+ ```
183
+
184
+ ### 3.3 Pydantic Integration
185
+
186
+ ```python
187
+ from pydantic import BaseModel, field_validator
188
+ from deal import pre
189
+ from returns.result import Result
190
+
191
+ class CreateUserRequest(BaseModel):
192
+ """Input validation via Pydantic."""
193
+ email: str
194
+ name: str
195
+
196
+ @field_validator('email')
197
+ def validate_email(cls, v):
198
+ if '@' not in v:
199
+ raise ValueError('Invalid email')
200
+ return v.lower()
201
+
202
+ # Invar contract for business rules
203
+ @pre(lambda req: not user_exists(req.email)) # Business rule
204
+ def create_user(req: CreateUserRequest) -> Result[User, CreateUserError]:
205
+ ...
206
+ ```
207
+
208
+ ---
209
+
210
+ ## 4. Core/Shell Separation
211
+
212
+ ### 4.1 Directory Structure
213
+
214
+ ```
215
+ src/myapp/
216
+ ├── core/ # Pure functions, no I/O
217
+ │ ├── __init__.py
218
+ │ ├── order/
219
+ │ │ ├── validation.py # @pre/@post + doctests
220
+ │ │ ├── calculation.py # Pure calculations
221
+ │ │ └── types.py # Domain types
222
+ │ └── user/
223
+ │ └── ...
224
+ └── shell/ # I/O operations
225
+ ├── __init__.py
226
+ ├── repositories/ # Data access
227
+ │ └── order_repo.py
228
+ ├── services/ # Business orchestration
229
+ │ └── order_service.py
230
+ └── api/ # HTTP layer
231
+ └── routes.py
232
+ ```
233
+
234
+ ### 4.2 Core Layer Example
235
+
236
+ ```python
237
+ # core/order/validation.py
238
+ from deal import pre, post
239
+ from returns.result import Result, Success, Failure
240
+ from .types import Order, ValidationError
241
+
242
+ @pre(lambda order: len(order.items) > 0)
243
+ def validate_order(order: Order) -> Result[Order, ValidationError]:
244
+ """
245
+ Validate order business rules.
246
+
247
+ >>> order = Order(items=[OrderItem(sku="A", qty=1)])
248
+ >>> validate_order(order).is_success()
249
+ True
250
+ >>> validate_order(Order(items=[])).is_failure()
251
+ True
252
+ """
253
+ for item in order.items:
254
+ if item.qty <= 0:
255
+ return Failure(ValidationError(field="qty", reason="must be positive"))
256
+ return Success(order)
257
+
258
+ def calculate_total(order: Order) -> float:
259
+ """
260
+ Calculate order total (pure function).
261
+
262
+ >>> order = Order(items=[OrderItem(sku="A", qty=2, price=10.0)])
263
+ >>> calculate_total(order)
264
+ 20.0
265
+ """
266
+ return sum(item.qty * item.price for item in order.items)
267
+ ```
268
+
269
+ ### 4.3 Shell Layer Example
270
+
271
+ ```python
272
+ # shell/services/order_service.py
273
+ from returns.result import Result
274
+ from myapp.core.order.validation import validate_order, calculate_total
275
+ from myapp.shell.repositories.order_repo import OrderRepository
276
+
277
+ class OrderService:
278
+ def __init__(self, repo: OrderRepository):
279
+ self._repo = repo
280
+
281
+ def process_order(self, order_id: str) -> Result[Receipt, OrderError]:
282
+ """Orchestrate Core functions and I/O operations."""
283
+ return (
284
+ self._repo.find(order_id) # Shell: I/O
285
+ .bind(validate_order) # Core: pure validation
286
+ .map(calculate_total) # Core: pure calculation
287
+ .bind(lambda total:
288
+ self._repo.save_total(order_id, total)) # Shell: I/O
289
+ )
290
+ ```
291
+
292
+ ---
293
+
294
+ ## 5. FastAPI Integration
295
+
296
+ ```python
297
+ from fastapi import FastAPI, HTTPException
298
+ from returns.result import Result
299
+
300
+ app = FastAPI()
301
+
302
+ def result_to_response(result: Result):
303
+ """Convert Result to HTTP response."""
304
+ if result.is_success():
305
+ return result.unwrap()
306
+
307
+ error = result.failure()
308
+ if isinstance(error, NotFoundError):
309
+ raise HTTPException(status_code=404, detail=error.message)
310
+ elif isinstance(error, ValidationError):
311
+ raise HTTPException(status_code=400, detail=error.message)
312
+ else:
313
+ raise HTTPException(status_code=500, detail="Internal error")
314
+
315
+ @app.get("/users/{user_id}")
316
+ async def get_user_endpoint(user_id: str):
317
+ result = user_service.get_user(user_id)
318
+ return result_to_response(result)
319
+ ```
320
+
321
+ ---
322
+
323
+ ## 6. Must Keep `raise` Scenarios
324
+
325
+ ```python
326
+ # 1. WSGI/ASGI error handlers (framework expects exceptions)
327
+ # 2. Click/Typer CLI (uses exceptions for flow control)
328
+ # 3. pytest fixtures (uses exceptions)
329
+ # 4. Context managers (__enter__/__exit__)
330
+ ```
331
+
332
+ ---
333
+
334
+ ## 7. Migration Checklist
335
+
336
+ - [ ] Install `returns` library: `pip install returns`
337
+ - [ ] Define error type hierarchy (`@dataclass` or Pydantic)
338
+ - [ ] Transform entry points to return `Result[T, E]`
339
+ - [ ] Extract pure functions to `core/` directory
340
+ - [ ] Add `@pre/@post` contracts to Core functions
341
+ - [ ] Add doctests to all Core functions
342
+ - [ ] Run `invar guard` to verify
343
+ - [ ] Update API handlers to use `result_to_response`
344
+
345
+ ---
346
+
347
+ *Pattern Library v1.0 — LX-09*