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 +21 -0
- llmcraft-0.1.0/PKG-INFO +487 -0
- llmcraft-0.1.0/README.md +453 -0
- llmcraft-0.1.0/pyproject.toml +77 -0
- llmcraft-0.1.0/specwright/__init__.py +45 -0
- llmcraft-0.1.0/specwright/cli.py +664 -0
- llmcraft-0.1.0/specwright/decorators.py +227 -0
- llmcraft-0.1.0/specwright/exceptions.py +49 -0
- llmcraft-0.1.0/specwright/pytest_plugin.py +79 -0
- llmcraft-0.1.0/specwright/state_machine.py +157 -0
- llmcraft-0.1.0/specwright/templates/__init__.py +0 -0
- llmcraft-0.1.0/specwright/templates/function.py.j2 +14 -0
- llmcraft-0.1.0/specwright/templates/function_test.py.j2 +27 -0
- llmcraft-0.1.0/specwright/templates/init_module.py.j2 +9 -0
- llmcraft-0.1.0/specwright/templates/init_pyproject.toml.j2 +21 -0
- llmcraft-0.1.0/specwright/templates/statemachine.py.j2 +18 -0
- llmcraft-0.1.0/specwright/templates/statemachine_test.py.j2 +28 -0
- llmcraft-0.1.0/specwright/testing.py +108 -0
- llmcraft-0.1.0/specwright/validation.py +123 -0
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.
|
llmcraft-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://specwright.org)
|
|
37
|
+
[](https://pypi.org/project/llmcraft/)
|
|
38
|
+
[](https://pypi.org/project/llmcraft/)
|
|
39
|
+
[](https://github.com/burakdalgic/Specwright/actions/workflows/test.yml)
|
|
40
|
+
[](https://github.com/burakdalgic/Specwright/actions/workflows/lint.yml)
|
|
41
|
+
[](https://codecov.io/gh/burakdalgic/Specwright)
|
|
42
|
+
[](LICENSE)
|
|
43
|
+
[](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
|
+
|