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,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))