llmcraft 0.1.0__tar.gz

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.
llmcraft-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Burak Dalgic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,487 @@
1
+ Metadata-Version: 2.4
2
+ Name: llmcraft
3
+ Version: 0.1.0
4
+ Summary: Specwright - Python framework for LLM-assisted development with runtime spec validation
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: specwright,llm,ai,code-generation,validation,testing,framework
8
+ Author: Burak burak@dalgic.dev
9
+ Requires-Python: >=3.11,<4.0
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Provides-Extra: diagrams
21
+ Requires-Dist: click (>=8.1,<9.0)
22
+ Requires-Dist: graphviz (>=0.20,<0.21) ; extra == "diagrams"
23
+ Requires-Dist: jinja2 (>=3.1,<4.0)
24
+ Requires-Dist: pydantic (>=2.0,<3.0)
25
+ Requires-Dist: rich (>=13.0,<14.0)
26
+ Requires-Dist: typing-extensions (>=4.0,<5.0)
27
+ Project-URL: Bug Tracker, https://github.com/burakdalgic/Specwright/issues
28
+ Project-URL: Changelog, https://github.com/burakdalgic/Specwright/blob/main/CHANGELOG.md
29
+ Project-URL: Documentation, https://specwright.org
30
+ Project-URL: Homepage, https://specwright.org
31
+ Project-URL: Repository, https://github.com/burakdalgic/Specwright
32
+ Description-Content-Type: text/markdown
33
+
34
+ # Specwright
35
+
36
+ [![Documentation](https://img.shields.io/badge/docs-specwright.org-blue)](https://specwright.org)
37
+ [![PyPI](https://img.shields.io/pypi/v/llmcraft)](https://pypi.org/project/llmcraft/)
38
+ [![Python](https://img.shields.io/pypi/pyversions/llmcraft)](https://pypi.org/project/llmcraft/)
39
+ [![Tests](https://github.com/burakdalgic/Specwright/actions/workflows/test.yml/badge.svg)](https://github.com/burakdalgic/Specwright/actions/workflows/test.yml)
40
+ [![Lint](https://github.com/burakdalgic/Specwright/actions/workflows/lint.yml/badge.svg)](https://github.com/burakdalgic/Specwright/actions/workflows/lint.yml)
41
+ [![codecov](https://codecov.io/gh/burakdalgic/Specwright/branch/main/graph/badge.svg)](https://codecov.io/gh/burakdalgic/Specwright)
42
+ [![License](https://img.shields.io/github/license/burakdalgic/Specwright)](LICENSE)
43
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
44
+
45
+ **A specification-first framework for LLM-assisted development.**
46
+
47
+ Humans write specifications and constraints. LLMs write implementations. Specwright enforces that implementations satisfy specifications — at decoration time and at runtime.
48
+
49
+ > **[Read the full documentation at specwright.org](https://specwright.org)**
50
+
51
+ ## Quick Start
52
+
53
+ ```python
54
+ from specwright import spec
55
+
56
+ @spec
57
+ def add(x: int, y: int) -> int:
58
+ """Add two integers."""
59
+ return x + y
60
+
61
+ add(1, 2) # 3
62
+ add("one", 2) # raises InputValidationError
63
+ ```
64
+
65
+ Every `@spec`-decorated function must have:
66
+ - **Complete type annotations** on all parameters and the return type
67
+ - **A docstring** describing its behavior
68
+
69
+ Specwright validates these at decoration time. At runtime, it checks that actual arguments and return values match the declared types.
70
+
71
+ ## Installation
72
+
73
+ ```bash
74
+ pip install llmcraft
75
+ ```
76
+
77
+ Or with [Poetry](https://python-poetry.org/):
78
+
79
+ ```bash
80
+ poetry add llmcraft
81
+ ```
82
+
83
+ > **Note:** The PyPI package name is `llmcraft`, but you import it as `specwright`.
84
+
85
+ > Requires Python 3.11+
86
+
87
+ ## Features
88
+
89
+ ### Runtime Type Validation
90
+
91
+ ```python
92
+ @spec
93
+ def divide(a: float, b: float) -> float:
94
+ """Divide a by b."""
95
+ return a / b
96
+
97
+ divide(10, 3) # 3.333...
98
+ divide("ten", 3) # InputValidationError with clear message
99
+ ```
100
+
101
+ ### Complex Type Support
102
+
103
+ Works with generics, unions, and Pydantic models:
104
+
105
+ ```python
106
+ from pydantic import BaseModel
107
+
108
+ class User(BaseModel):
109
+ name: str
110
+ age: int
111
+
112
+ @spec
113
+ def find_adults(users: list[User]) -> list[str]:
114
+ """Return names of users aged 18+."""
115
+ return [u.name for u in users if u.age >= 18]
116
+ ```
117
+
118
+ ### Spec Metadata for Doc Generation
119
+
120
+ Every decorated function carries machine-readable metadata:
121
+
122
+ ```python
123
+ @spec
124
+ def greet(name: str) -> str:
125
+ """Greet someone by name."""
126
+ return f"Hello, {name}!"
127
+
128
+ meta = greet.__spec__
129
+ meta.name # "greet"
130
+ meta.parameters # {"name": <class 'str'>}
131
+ meta.return_type # <class 'str'>
132
+ meta.docstring # "Greet someone by name."
133
+ ```
134
+
135
+ ### Configurable Enforcement
136
+
137
+ Turn off specific checks when you need to:
138
+
139
+ ```python
140
+ @spec(validate_output=False)
141
+ def flexible_return(x: int) -> str:
142
+ """May not return a string during development."""
143
+ return x # no error raised
144
+ ```
145
+
146
+ ### Declarative Error Handling
147
+
148
+ The `@handle_errors` decorator maps exception types to handling strategies:
149
+
150
+ ```python
151
+ from specwright import handle_errors
152
+
153
+ @handle_errors({
154
+ ValueError: "ignore", # suppress, return None
155
+ KeyError: lambda e: f"missing: {e}", # custom handler
156
+ RuntimeError: "log", # log with traceback, re-raise
157
+ ConnectionError: {"error": "offline"}, # return a fallback value
158
+ })
159
+ def process(data: dict) -> str:
160
+ ...
161
+ ```
162
+
163
+ **Strategies:**
164
+
165
+ | Strategy | Behaviour |
166
+ |---|---|
167
+ | `"ignore"` | Suppress the exception, return `None` |
168
+ | `"log"` | Log with full traceback, then re-raise |
169
+ | callable | Call `handler(exception)`, return its result |
170
+ | any other value | Return that value directly |
171
+
172
+ ### Combining `@spec` and `@handle_errors`
173
+
174
+ The two decorators compose naturally. Place `@handle_errors` on the outside to catch exceptions that escape the spec-validated function:
175
+
176
+ ```python
177
+ from specwright import spec, handle_errors
178
+
179
+ @handle_errors({
180
+ ValueError: lambda e: {"error": str(e)},
181
+ KeyError: "ignore",
182
+ })
183
+ @spec
184
+ def get_user(user_id: int) -> dict:
185
+ """Look up a user by ID."""
186
+ if user_id < 0:
187
+ raise ValueError("user_id must be non-negative")
188
+ return USERS[user_id]
189
+
190
+ get_user(1) # {"name": "Alice", ...}
191
+ get_user(-1) # {"error": "user_id must be non-negative"}
192
+ get_user(999) # None (KeyError ignored)
193
+ get_user("bad") # raises InputValidationError (not in handlers)
194
+ ```
195
+
196
+ Or place `@spec` on the outside to validate the fallback return values too:
197
+
198
+ ```python
199
+ @spec
200
+ @handle_errors({ValueError: 0})
201
+ def parse_int(s: str) -> int:
202
+ """Parse a string to int, defaulting to 0."""
203
+ return int(s)
204
+
205
+ parse_int("abc") # 0 (fallback passes int type check)
206
+ ```
207
+
208
+ ### State Machine
209
+
210
+ The `StateMachine` base class enforces valid state transitions at runtime:
211
+
212
+ ```python
213
+ from specwright import StateMachine, transition
214
+
215
+ class OrderProcessor(StateMachine):
216
+ states = ["pending", "paid", "shipped", "delivered", "cancelled"]
217
+ initial_state = "pending"
218
+
219
+ @transition(from_state="pending", to_state="paid")
220
+ def pay(self, amount: float) -> str:
221
+ return f"Paid ${amount:.2f}"
222
+
223
+ @transition(from_state="paid", to_state="shipped")
224
+ def ship(self, tracking: str) -> str:
225
+ return f"Shipped ({tracking})"
226
+
227
+ @transition(from_state=["pending", "paid"], to_state="cancelled")
228
+ def cancel(self, reason: str) -> str:
229
+ return f"Cancelled: {reason}"
230
+
231
+ order = OrderProcessor()
232
+ order.pay(99.99) # state -> "paid"
233
+ order.ship("TRACK-123") # state -> "shipped"
234
+ order.cancel("reason") # raises InvalidTransitionError (can't cancel after shipping)
235
+ ```
236
+
237
+ **State history** tracks every state visited:
238
+
239
+ ```python
240
+ class Tracked(StateMachine):
241
+ states = ["a", "b", "c"]
242
+ initial_state = "a"
243
+ track_history = True # opt-in
244
+
245
+ @transition(from_state="a", to_state="b")
246
+ def go_b(self): ...
247
+
248
+ @transition(from_state="b", to_state="c")
249
+ def go_c(self): ...
250
+
251
+ sm = Tracked()
252
+ sm.go_b()
253
+ sm.go_c()
254
+ sm.state_history # ["a", "b", "c"]
255
+ ```
256
+
257
+ **Lifecycle hooks** run automatically on state changes:
258
+
259
+ ```python
260
+ class WithHooks(StateMachine):
261
+ states = ["active", "suspended"]
262
+ initial_state = "active"
263
+
264
+ @transition(from_state="active", to_state="suspended")
265
+ def suspend(self): ...
266
+
267
+ def on_exit_active(self):
268
+ print("Leaving active state")
269
+
270
+ def on_enter_suspended(self):
271
+ print("Entering suspended state")
272
+ ```
273
+
274
+ **Combines with `@spec`** for full validation:
275
+
276
+ ```python
277
+ class Machine(StateMachine):
278
+ states = ["idle", "done"]
279
+ initial_state = "idle"
280
+
281
+ @transition(from_state="idle", to_state="done")
282
+ @spec
283
+ def finish(self, result: str) -> str:
284
+ """Complete the task."""
285
+ return f"done: {result}"
286
+ ```
287
+
288
+ State machines help LLMs by making valid transitions explicit and machine-readable. An LLM can see exactly which states exist, which transitions are allowed, and what the current state is — eliminating an entire class of bugs where code attempts an impossible operation.
289
+
290
+ ### Test Requirements (`@requires_tests`)
291
+
292
+ Declare what tests a function needs — the pytest plugin enforces it:
293
+
294
+ ```python
295
+ from specwright import requires_tests, spec
296
+
297
+ @requires_tests(
298
+ happy_path=True,
299
+ edge_cases=["empty_input", "max_boundaries"],
300
+ error_cases=["invalid_email", "negative_age"],
301
+ )
302
+ @spec
303
+ def create_user(email: str, age: int) -> dict:
304
+ """Create a new user account."""
305
+ ...
306
+ ```
307
+
308
+ The decorator stores a `TestRequirements` object on the function:
309
+
310
+ ```python
311
+ reqs = create_user.__test_requirements__
312
+ reqs.expected_test_names
313
+ # ['test_create_user_happy_path',
314
+ # 'test_create_user_empty_input',
315
+ # 'test_create_user_max_boundaries',
316
+ # 'test_create_user_invalid_email',
317
+ # 'test_create_user_negative_age']
318
+ ```
319
+
320
+ **Naming convention:** `test_{function_name}_{case_name}`
321
+
322
+ **Pytest plugin** verifies at collection time that all required test functions exist:
323
+
324
+ ```toml
325
+ # pyproject.toml
326
+ [tool.pytest.ini_options]
327
+ specwright_test_enforcement = "strict" # "strict" | "warn" | "off"
328
+ ```
329
+
330
+ | Mode | Behaviour |
331
+ |---|---|
332
+ | `strict` | Fail the session if any required tests are missing |
333
+ | `warn` | Emit warnings but let the session continue |
334
+ | `off` | Skip the check entirely |
335
+
336
+ This enforces a **test-driven LLM workflow**: humans declare *what* must be tested, LLMs write the implementations *and* the tests, and the framework ensures nothing is forgotten.
337
+
338
+ ### Clear Error Messages
339
+
340
+ ```
341
+ InputValidationError: Input validation failed for 'add':
342
+ - Parameter 'x': expected <class 'int'>, got str ('one')
343
+ ```
344
+
345
+ ```
346
+ InvalidTransitionError: Cannot transition from 'shipped' to 'cancelled'
347
+ via 'cancel'. Valid source state(s): paid, pending
348
+ ```
349
+
350
+ ## Why Specwright?
351
+
352
+ Modern development increasingly involves LLMs generating code. This creates a new problem: **how do you trust LLM-generated implementations?**
353
+
354
+ The traditional answer — code review — doesn't scale. Specwright takes a different approach:
355
+
356
+ 1. **Humans write specs** — type signatures, docstrings, constraints, and state machines that define *what* a function should do
357
+ 2. **LLMs write implementations** — the code that fulfills the spec
358
+ 3. **Specwright enforces correctness** — runtime validation ensures implementations actually satisfy their specifications
359
+
360
+ This creates a workflow where humans stay in control of *what* the software does, while delegating *how* it does it. The framework is the bridge that ensures the two stay in sync.
361
+
362
+ ## CLI
363
+
364
+ Specwright includes a CLI for scaffolding projects, generating boilerplate, validating coverage, and producing docs.
365
+
366
+ ### `specwright init`
367
+
368
+ Scaffold a new project:
369
+
370
+ ```bash
371
+ specwright init my_project
372
+ ```
373
+
374
+ Creates `my_project/` with `pyproject.toml`, a sample `@spec` function, `tests/`, and a `.specwright.toml` config file.
375
+
376
+ ### `specwright new function`
377
+
378
+ Generate a `@spec`-decorated function and its test file:
379
+
380
+ ```bash
381
+ specwright new function calculate_score \
382
+ --params "base: int, multiplier: float" \
383
+ --returns float
384
+ ```
385
+
386
+ Omit `--params` / `--returns` to be prompted interactively.
387
+
388
+ ### `specwright new statemachine`
389
+
390
+ Generate a `StateMachine` subclass with sequential transitions:
391
+
392
+ ```bash
393
+ specwright new statemachine order_processor \
394
+ --states pending,paid,shipped,delivered
395
+ ```
396
+
397
+ ### `specwright validate`
398
+
399
+ Check that all `@spec`-decorated functions have tests and state machines are well-formed:
400
+
401
+ ```bash
402
+ specwright validate --path .
403
+ ```
404
+
405
+ Exits with code 1 if issues are found.
406
+
407
+ ### `specwright docs`
408
+
409
+ Generate API documentation from `@spec` metadata:
410
+
411
+ ```bash
412
+ specwright docs --path . # to stdout
413
+ specwright docs --path . --output API.md # to file
414
+ specwright docs --path . --diagram # include DOT state diagrams
415
+ ```
416
+
417
+ ### Workflow
418
+
419
+ ```bash
420
+ specwright init my_project
421
+ cd my_project
422
+ specwright new function my_func
423
+ specwright new statemachine my_workflow
424
+ # ... fill in implementations ...
425
+ specwright validate
426
+ specwright docs --output API.md
427
+ ```
428
+
429
+ ## Project Structure
430
+
431
+ ```
432
+ specwright/
433
+ __init__.py # Public API
434
+ cli.py # CLI entry point (init, new, validate, docs)
435
+ decorators.py # @spec and @handle_errors decorators
436
+ state_machine.py # StateMachine base class and @transition
437
+ testing.py # @requires_tests decorator and TestRequirements
438
+ pytest_plugin.py # Pytest plugin for test enforcement
439
+ validation.py # Runtime type checking engine
440
+ exceptions.py # Clear, typed error hierarchy
441
+ templates/ # Jinja2 templates for code generation
442
+ tests/ # Comprehensive test suite
443
+ examples/ # Runnable usage examples
444
+ ```
445
+
446
+ ## Development
447
+
448
+ ```bash
449
+ # Install dependencies
450
+ poetry install
451
+
452
+ # Run tests
453
+ poetry run pytest
454
+
455
+ # Run tests with coverage
456
+ poetry run pytest --cov=specwright --cov-report=term-missing
457
+
458
+ # Lint
459
+ poetry run ruff check .
460
+
461
+ # Format
462
+ poetry run black .
463
+
464
+ # Type check
465
+ poetry run mypy specwright
466
+ ```
467
+
468
+ ## Contributing
469
+
470
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code style, and PR guidelines.
471
+
472
+ ## License
473
+
474
+ MIT — see [LICENSE](LICENSE) for details.
475
+
476
+ ## Why is the package called llmcraft?
477
+
478
+ The project is called Specwright, but the PyPI package name `specwright` was already taken.
479
+ We use `llmcraft` on PyPI while maintaining Specwright as our brand and import name.
480
+
481
+ ## Links
482
+
483
+ - **Documentation:** [specwright.org](https://specwright.org)
484
+ - **Source:** [GitHub](https://github.com/burakdalgic/Specwright)
485
+ - **Issues:** [Bug Tracker](https://github.com/burakdalgic/Specwright/issues)
486
+ - **Changelog:** [CHANGELOG.md](CHANGELOG.md)
487
+