masterwork 0.0.3__tar.gz → 0.0.5__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.
- {masterwork-0.0.3/masterwork.egg-info → masterwork-0.0.5}/PKG-INFO +33 -32
- {masterwork-0.0.3 → masterwork-0.0.5}/README.md +11 -10
- {masterwork-0.0.3 → masterwork-0.0.5}/README.pypi.md +31 -30
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/__init__.py +1 -1
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/__main__.py +33 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/measure.py +17 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/seal.py +77 -7
- masterwork-0.0.5/masterwork/starter/script.json +19 -0
- masterwork-0.0.5/masterwork/starter/tales.md +77 -0
- {masterwork-0.0.3 → masterwork-0.0.5/masterwork.egg-info}/PKG-INFO +33 -32
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork.egg-info/SOURCES.txt +2 -0
- masterwork-0.0.5/masterwork.egg-info/requires.txt +1 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/pyproject.toml +7 -3
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_cli.py +16 -1
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_line.py +1 -1
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_measure.py +18 -2
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_seal.py +81 -0
- masterwork-0.0.3/masterwork.egg-info/requires.txt +0 -1
- {masterwork-0.0.3 → masterwork-0.0.5}/LICENSE +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/blind_label.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/campaign.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/cells.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/ceremony.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/gate.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/line.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/pairs.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/retain.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork/rubric-example.json +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork.egg-info/dependency_links.txt +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork.egg-info/entry_points.txt +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/masterwork.egg-info/top_level.txt +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/setup.cfg +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_badges.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_blind_label.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_campaign.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_cells.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_ceremony.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_examples.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_gate.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_packaging.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_pairs.py +0 -0
- {masterwork-0.0.3 → masterwork-0.0.5}/tests/test_retain.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: masterwork
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: A production line for agent character: a sitting the model speaks for itself in, sealed candidates, frozen gates, and a verdict it is not allowed to give itself.
|
|
5
5
|
Author: Codechu
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -22,14 +22,14 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
22
22
|
Requires-Python: >=3.10
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
|
-
Requires-Dist: journeyman-bench
|
|
25
|
+
Requires-Dist: journeyman-bench<0.5.0,>=0.2.1
|
|
26
26
|
Dynamic: license-file
|
|
27
27
|
|
|
28
|
-
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.
|
|
28
|
+
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/banner.jpg" alt="A craftsman slides a sealed folio across his bench onto the guild's empty judging table; earlier pieces stand turned to the wall behind him." width="100%"></p>
|
|
29
29
|
|
|
30
30
|
# Masterwork
|
|
31
31
|
|
|
32
|
-
[](#run-it) [](https://github.com/codechu/masterwork/blob/v0.0.
|
|
32
|
+
[](#run-it) [](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE) [](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md#ground-rules) [](#status)
|
|
33
33
|
|
|
34
34
|
*Makes a character for a model. Does not score it.*
|
|
35
35
|
|
|
@@ -61,7 +61,7 @@ masterwork # what it is, and its stages
|
|
|
61
61
|
masterwork gate gates/ # check the gate templates
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
64
|
+
Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md).
|
|
65
65
|
|
|
66
66
|
Or from a clone, with nothing installed at all:
|
|
67
67
|
|
|
@@ -117,7 +117,7 @@ record, and exits in a way that says *waiting*, not *broken*.
|
|
|
117
117
|
| `HELD_WITHOUT_PURPOSE` | the spec does not say what the run decides | write `purpose`; if you cannot say what changes on each outcome, the run is not ready |
|
|
118
118
|
| `HELD_AT_SEAL` | the piece is unreproducible, or the deployed copy differs | fix the seal or the deployment — never run "just to see" |
|
|
119
119
|
| `HELD_AT_COMPLETENESS` | the grid is short | find the missing cells; if proceeding anyway, declare it |
|
|
120
|
-
| `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
120
|
+
| `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/blind_label.py); the line is waiting, not broken |
|
|
121
121
|
| `HELD_AT_MEASUREMENT` | the benchmark says the score is not comparable | judge from a separate endpoint, or declare and keep the stamp |
|
|
122
122
|
| `HELD_AT_GATE` | a threshold sits inside its own noise | the gate is void; measure the band, write a new gate |
|
|
123
123
|
| `HELD_WITHOUT_MEASUREMENT` | a rule names an axis and nothing measured it | judge with the benchmark, or declare the run diagnostic — a gate that was checked is not a gate that was applied |
|
|
@@ -169,15 +169,15 @@ live under `runs/`, which version control ignores, and the seal names
|
|
|
169
169
|
everything needed to re-derive them. The run record and the benchmark's report
|
|
170
170
|
are archived on purpose — both are kilobytes, and between them they carry the
|
|
171
171
|
seal, the grid, the axes, the gate and the verdict. That is what a later
|
|
172
|
-
disagreement is settled with. `
|
|
172
|
+
disagreement is settled with. `masterwork/retain.py archive <run-dir> <dest>`
|
|
173
173
|
copies out exactly those two; oversized run directories are reported, never
|
|
174
174
|
deleted, because deciding what to remove is not the line's call.
|
|
175
175
|
|
|
176
176
|
## Making a piece
|
|
177
177
|
|
|
178
|
-
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.
|
|
178
|
+
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/ceremony.jpg" alt="A figure writes alone by lantern light; every earlier work in the room is draped and turned away, and a stick of sealing wax lies unused on the table." width="520"></p>
|
|
179
179
|
|
|
180
|
-
`
|
|
180
|
+
`masterwork/ceremony.py` runs the sitting. Four properties are the recipe rather
|
|
181
181
|
than implementation detail, and each was paid for:
|
|
182
182
|
|
|
183
183
|
- **Anchorless.** No earlier commitment or name is anywhere in the context.
|
|
@@ -222,10 +222,10 @@ You get the line and its gates, the shape of the sitting, and the method
|
|
|
222
222
|
written down: how a tale differs from an instruction, where a tale comes from,
|
|
223
223
|
what a question has to do, why instructions come last, and how to tag a tale
|
|
224
224
|
whose effect cannot be separated. That is
|
|
225
|
-
[`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
225
|
+
[`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md), and it is the part that transfers.
|
|
226
226
|
|
|
227
227
|
You do not get a corpus. Three pairs come with the repository in
|
|
228
|
-
[`
|
|
228
|
+
[`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) so the machinery runs end to end and the
|
|
229
229
|
form is unambiguous; they are a beginning and they are meant to be outgrown.
|
|
230
230
|
The corpus is the work, and it has to be yours: a tale is written from
|
|
231
231
|
something **you** measured and got wrong, and a corpus assembled from someone
|
|
@@ -281,38 +281,39 @@ the position — a masterwork is submitted, and it can be refused.
|
|
|
281
281
|
|
|
282
282
|
| document | when you need it |
|
|
283
283
|
|---|---|
|
|
284
|
-
| [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
285
|
-
| [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
286
|
-
| [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
287
|
-
| [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
288
|
-
| [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
289
|
-
| [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
290
|
-
| [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
291
|
-
| [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
292
|
-
| [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
293
|
-
| [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
294
|
-
| [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.
|
|
284
|
+
| [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md) | **start here** — nothing to a sealed candidate, one command at a time |
|
|
285
|
+
| [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.5/AGENTS.md) | before running anything — the refusals, the vocabulary, and the four things not to do. Read it whether or not you are an agent |
|
|
286
|
+
| [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/ceremony.md) | making a piece: the four properties of the sitting, and the script that drives it |
|
|
287
|
+
| [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/seal.md) | the five fields a candidate carries, and what to do when the gate refuses one |
|
|
288
|
+
| [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/run-spec.md) | writing the JSON for one run, field by field |
|
|
289
|
+
| [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/pairs.md) | turning a scored run into training data, and what a run leaves behind |
|
|
290
|
+
| [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
|
|
291
|
+
| [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.5/gates/succession.md) |
|
|
292
|
+
| [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md) | before writing a single tale — the shape is in [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) |
|
|
293
|
+
| [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) | every entry names the incident that caused it |
|
|
294
|
+
| [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.5/assets/README.md) | you need a fifth image that matches the four |
|
|
295
295
|
|
|
296
296
|
<details>
|
|
297
297
|
<summary>Layout</summary>
|
|
298
298
|
|
|
299
299
|
```text
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
gates/
|
|
303
|
-
examples/
|
|
304
|
-
corpus/
|
|
305
|
-
|
|
300
|
+
masterwork/ the line and every stage it walks
|
|
301
|
+
masterwork/starter/ three tale-and-question pairs, shipped so you can begin
|
|
302
|
+
gates/ frozen gate templates — example.md is the smallest one
|
|
303
|
+
examples/ runnable fixtures; the trace above comes from here
|
|
304
|
+
corpus/ how a corpus grows, and what an invariant costs
|
|
305
|
+
docs/ reference, and one page that is a path
|
|
306
|
+
runs/ local run directories (ignored — heavy and re-derivable)
|
|
306
307
|
```
|
|
307
308
|
|
|
308
309
|
</details>
|
|
309
310
|
|
|
310
311
|
---
|
|
311
312
|
|
|
312
|
-
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.
|
|
313
|
+
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
|
|
313
314
|
|
|
314
|
-
Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.
|
|
315
|
-
[CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.
|
|
316
|
-
[Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.
|
|
315
|
+
Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md) · Changelog:
|
|
316
|
+
[CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) · Licensed under the
|
|
317
|
+
[Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE).
|
|
317
318
|
|
|
318
319
|
Part of [Codechu](https://github.com/codechu).
|
|
@@ -142,7 +142,7 @@ live under `runs/`, which version control ignores, and the seal names
|
|
|
142
142
|
everything needed to re-derive them. The run record and the benchmark's report
|
|
143
143
|
are archived on purpose — both are kilobytes, and between them they carry the
|
|
144
144
|
seal, the grid, the axes, the gate and the verdict. That is what a later
|
|
145
|
-
disagreement is settled with. `
|
|
145
|
+
disagreement is settled with. `masterwork/retain.py archive <run-dir> <dest>`
|
|
146
146
|
copies out exactly those two; oversized run directories are reported, never
|
|
147
147
|
deleted, because deciding what to remove is not the line's call.
|
|
148
148
|
|
|
@@ -150,7 +150,7 @@ deleted, because deciding what to remove is not the line's call.
|
|
|
150
150
|
|
|
151
151
|
<p align="center"><img src="assets/ceremony.jpg" alt="A figure writes alone by lantern light; every earlier work in the room is draped and turned away, and a stick of sealing wax lies unused on the table." width="520"></p>
|
|
152
152
|
|
|
153
|
-
`
|
|
153
|
+
`masterwork/ceremony.py` runs the sitting. Four properties are the recipe rather
|
|
154
154
|
than implementation detail, and each was paid for:
|
|
155
155
|
|
|
156
156
|
- **Anchorless.** No earlier commitment or name is anywhere in the context.
|
|
@@ -198,7 +198,7 @@ whose effect cannot be separated. That is
|
|
|
198
198
|
[`corpus/GROWING.md`](corpus/GROWING.md), and it is the part that transfers.
|
|
199
199
|
|
|
200
200
|
You do not get a corpus. Three pairs come with the repository in
|
|
201
|
-
[`
|
|
201
|
+
[`masterwork/starter/`](masterwork/starter/) so the machinery runs end to end and the
|
|
202
202
|
form is unambiguous; they are a beginning and they are meant to be outgrown.
|
|
203
203
|
The corpus is the work, and it has to be yours: a tale is written from
|
|
204
204
|
something **you** measured and got wrong, and a corpus assembled from someone
|
|
@@ -262,7 +262,7 @@ the position — a masterwork is submitted, and it can be refused.
|
|
|
262
262
|
| [`docs/pairs.md`](docs/pairs.md) | turning a scored run into training data, and what a run leaves behind |
|
|
263
263
|
| [`docs/campaigns.md`](docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
|
|
264
264
|
| [`docs/gates.md`](docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](gates/succession.md) |
|
|
265
|
-
| [`corpus/GROWING.md`](corpus/GROWING.md) | before writing a single tale — the shape is in [`
|
|
265
|
+
| [`corpus/GROWING.md`](corpus/GROWING.md) | before writing a single tale — the shape is in [`masterwork/starter/`](masterwork/starter/) |
|
|
266
266
|
| [`CHANGELOG.md`](CHANGELOG.md) | every entry names the incident that caused it |
|
|
267
267
|
| [assets/README.md](assets/README.md) | you need a fifth image that matches the four |
|
|
268
268
|
|
|
@@ -270,12 +270,13 @@ the position — a masterwork is submitted, and it can be refused.
|
|
|
270
270
|
<summary>Layout</summary>
|
|
271
271
|
|
|
272
272
|
```text
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
gates/
|
|
276
|
-
examples/
|
|
277
|
-
corpus/
|
|
278
|
-
|
|
273
|
+
masterwork/ the line and every stage it walks
|
|
274
|
+
masterwork/starter/ three tale-and-question pairs, shipped so you can begin
|
|
275
|
+
gates/ frozen gate templates — example.md is the smallest one
|
|
276
|
+
examples/ runnable fixtures; the trace above comes from here
|
|
277
|
+
corpus/ how a corpus grows, and what an invariant costs
|
|
278
|
+
docs/ reference, and one page that is a path
|
|
279
|
+
runs/ local run directories (ignored — heavy and re-derivable)
|
|
279
280
|
```
|
|
280
281
|
|
|
281
282
|
</details>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.
|
|
1
|
+
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/banner.jpg" alt="A craftsman slides a sealed folio across his bench onto the guild's empty judging table; earlier pieces stand turned to the wall behind him." width="100%"></p>
|
|
2
2
|
|
|
3
3
|
# Masterwork
|
|
4
4
|
|
|
5
|
-
[](#run-it) [](https://github.com/codechu/masterwork/blob/v0.0.
|
|
5
|
+
[](#run-it) [](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE) [](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md#ground-rules) [](#status)
|
|
6
6
|
|
|
7
7
|
*Makes a character for a model. Does not score it.*
|
|
8
8
|
|
|
@@ -34,7 +34,7 @@ masterwork # what it is, and its stages
|
|
|
34
34
|
masterwork gate gates/ # check the gate templates
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
37
|
+
Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md).
|
|
38
38
|
|
|
39
39
|
Or from a clone, with nothing installed at all:
|
|
40
40
|
|
|
@@ -90,7 +90,7 @@ record, and exits in a way that says *waiting*, not *broken*.
|
|
|
90
90
|
| `HELD_WITHOUT_PURPOSE` | the spec does not say what the run decides | write `purpose`; if you cannot say what changes on each outcome, the run is not ready |
|
|
91
91
|
| `HELD_AT_SEAL` | the piece is unreproducible, or the deployed copy differs | fix the seal or the deployment — never run "just to see" |
|
|
92
92
|
| `HELD_AT_COMPLETENESS` | the grid is short | find the missing cells; if proceeding anyway, declare it |
|
|
93
|
-
| `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
93
|
+
| `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/blind_label.py); the line is waiting, not broken |
|
|
94
94
|
| `HELD_AT_MEASUREMENT` | the benchmark says the score is not comparable | judge from a separate endpoint, or declare and keep the stamp |
|
|
95
95
|
| `HELD_AT_GATE` | a threshold sits inside its own noise | the gate is void; measure the band, write a new gate |
|
|
96
96
|
| `HELD_WITHOUT_MEASUREMENT` | a rule names an axis and nothing measured it | judge with the benchmark, or declare the run diagnostic — a gate that was checked is not a gate that was applied |
|
|
@@ -142,15 +142,15 @@ live under `runs/`, which version control ignores, and the seal names
|
|
|
142
142
|
everything needed to re-derive them. The run record and the benchmark's report
|
|
143
143
|
are archived on purpose — both are kilobytes, and between them they carry the
|
|
144
144
|
seal, the grid, the axes, the gate and the verdict. That is what a later
|
|
145
|
-
disagreement is settled with. `
|
|
145
|
+
disagreement is settled with. `masterwork/retain.py archive <run-dir> <dest>`
|
|
146
146
|
copies out exactly those two; oversized run directories are reported, never
|
|
147
147
|
deleted, because deciding what to remove is not the line's call.
|
|
148
148
|
|
|
149
149
|
## Making a piece
|
|
150
150
|
|
|
151
|
-
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.
|
|
151
|
+
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/ceremony.jpg" alt="A figure writes alone by lantern light; every earlier work in the room is draped and turned away, and a stick of sealing wax lies unused on the table." width="520"></p>
|
|
152
152
|
|
|
153
|
-
`
|
|
153
|
+
`masterwork/ceremony.py` runs the sitting. Four properties are the recipe rather
|
|
154
154
|
than implementation detail, and each was paid for:
|
|
155
155
|
|
|
156
156
|
- **Anchorless.** No earlier commitment or name is anywhere in the context.
|
|
@@ -195,10 +195,10 @@ You get the line and its gates, the shape of the sitting, and the method
|
|
|
195
195
|
written down: how a tale differs from an instruction, where a tale comes from,
|
|
196
196
|
what a question has to do, why instructions come last, and how to tag a tale
|
|
197
197
|
whose effect cannot be separated. That is
|
|
198
|
-
[`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
198
|
+
[`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md), and it is the part that transfers.
|
|
199
199
|
|
|
200
200
|
You do not get a corpus. Three pairs come with the repository in
|
|
201
|
-
[`
|
|
201
|
+
[`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) so the machinery runs end to end and the
|
|
202
202
|
form is unambiguous; they are a beginning and they are meant to be outgrown.
|
|
203
203
|
The corpus is the work, and it has to be yours: a tale is written from
|
|
204
204
|
something **you** measured and got wrong, and a corpus assembled from someone
|
|
@@ -254,38 +254,39 @@ the position — a masterwork is submitted, and it can be refused.
|
|
|
254
254
|
|
|
255
255
|
| document | when you need it |
|
|
256
256
|
|---|---|
|
|
257
|
-
| [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
258
|
-
| [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
259
|
-
| [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
260
|
-
| [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
261
|
-
| [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
262
|
-
| [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
263
|
-
| [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
264
|
-
| [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
265
|
-
| [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
266
|
-
| [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
267
|
-
| [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.
|
|
257
|
+
| [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md) | **start here** — nothing to a sealed candidate, one command at a time |
|
|
258
|
+
| [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.5/AGENTS.md) | before running anything — the refusals, the vocabulary, and the four things not to do. Read it whether or not you are an agent |
|
|
259
|
+
| [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/ceremony.md) | making a piece: the four properties of the sitting, and the script that drives it |
|
|
260
|
+
| [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/seal.md) | the five fields a candidate carries, and what to do when the gate refuses one |
|
|
261
|
+
| [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/run-spec.md) | writing the JSON for one run, field by field |
|
|
262
|
+
| [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/pairs.md) | turning a scored run into training data, and what a run leaves behind |
|
|
263
|
+
| [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
|
|
264
|
+
| [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.5/gates/succession.md) |
|
|
265
|
+
| [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md) | before writing a single tale — the shape is in [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) |
|
|
266
|
+
| [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) | every entry names the incident that caused it |
|
|
267
|
+
| [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.5/assets/README.md) | you need a fifth image that matches the four |
|
|
268
268
|
|
|
269
269
|
<details>
|
|
270
270
|
<summary>Layout</summary>
|
|
271
271
|
|
|
272
272
|
```text
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
gates/
|
|
276
|
-
examples/
|
|
277
|
-
corpus/
|
|
278
|
-
|
|
273
|
+
masterwork/ the line and every stage it walks
|
|
274
|
+
masterwork/starter/ three tale-and-question pairs, shipped so you can begin
|
|
275
|
+
gates/ frozen gate templates — example.md is the smallest one
|
|
276
|
+
examples/ runnable fixtures; the trace above comes from here
|
|
277
|
+
corpus/ how a corpus grows, and what an invariant costs
|
|
278
|
+
docs/ reference, and one page that is a path
|
|
279
|
+
runs/ local run directories (ignored — heavy and re-derivable)
|
|
279
280
|
```
|
|
280
281
|
|
|
281
282
|
</details>
|
|
282
283
|
|
|
283
284
|
---
|
|
284
285
|
|
|
285
|
-
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.
|
|
286
|
+
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
|
|
286
287
|
|
|
287
|
-
Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.
|
|
288
|
-
[CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.
|
|
289
|
-
[Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.
|
|
288
|
+
Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md) · Changelog:
|
|
289
|
+
[CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) · Licensed under the
|
|
290
|
+
[Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE).
|
|
290
291
|
|
|
291
292
|
Part of [Codechu](https://github.com/codechu).
|
|
@@ -17,6 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
import argparse
|
|
19
19
|
import importlib
|
|
20
|
+
import pathlib
|
|
20
21
|
import sys
|
|
21
22
|
|
|
22
23
|
from masterwork import __version__
|
|
@@ -39,6 +40,7 @@ COMMANDS = {
|
|
|
39
40
|
"pairs": ("masterwork.pairs", "cut training data from scored runs"),
|
|
40
41
|
"retain": ("masterwork.retain", "run-directory retention"),
|
|
41
42
|
"label": ("masterwork.blind_label", "blind-label cells for a judged axis"),
|
|
43
|
+
"starter": (None, "write out the starter corpus and its script"),
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
|
|
@@ -61,6 +63,34 @@ def banner(version: str) -> str:
|
|
|
61
63
|
])
|
|
62
64
|
|
|
63
65
|
|
|
66
|
+
def starter(argv) -> int:
|
|
67
|
+
"""Write the starter corpus somewhere the reader can use it.
|
|
68
|
+
|
|
69
|
+
The two files ship inside the package because a walkthrough that says
|
|
70
|
+
`cat corpus/starter/tales.md` is only true for someone who cloned the
|
|
71
|
+
repository — and the whole point of a starter corpus is that nobody has
|
|
72
|
+
to write one before they can begin.
|
|
73
|
+
"""
|
|
74
|
+
import argparse as _a
|
|
75
|
+
import shutil
|
|
76
|
+
ap = _a.ArgumentParser(prog="masterwork starter",
|
|
77
|
+
description="write out the starter corpus and its script")
|
|
78
|
+
ap.add_argument("--to", default=".", help="directory to write into (default: here)")
|
|
79
|
+
a = ap.parse_args(argv)
|
|
80
|
+
src = pathlib.Path(__file__).resolve().parent / "starter"
|
|
81
|
+
dst = pathlib.Path(a.to)
|
|
82
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
for name in ("tales.md", "script.json"):
|
|
84
|
+
target = dst / name
|
|
85
|
+
if target.exists():
|
|
86
|
+
print(f"masterwork starter: {target} already exists — not overwriting",
|
|
87
|
+
file=sys.stderr)
|
|
88
|
+
return 2
|
|
89
|
+
shutil.copyfile(src / name, target)
|
|
90
|
+
print(target)
|
|
91
|
+
return 0
|
|
92
|
+
|
|
93
|
+
|
|
64
94
|
def build_parser() -> argparse.ArgumentParser:
|
|
65
95
|
ap = argparse.ArgumentParser(
|
|
66
96
|
prog="masterwork",
|
|
@@ -105,6 +135,9 @@ def main(argv=None) -> int:
|
|
|
105
135
|
if not quiet:
|
|
106
136
|
print(banner(__version__), file=sys.stderr)
|
|
107
137
|
|
|
138
|
+
if cmd == "starter":
|
|
139
|
+
return starter(argv[at + 1:])
|
|
140
|
+
|
|
108
141
|
module = importlib.import_module(COMMANDS[cmd][0])
|
|
109
142
|
# The stage's parser takes its name from argv[0]; give it one a person
|
|
110
143
|
# could have typed instead of the interpreter's path.
|
|
@@ -138,8 +138,25 @@ def newest_report(runs_dir: str) -> str | None:
|
|
|
138
138
|
return max(found)[1] if found else None
|
|
139
139
|
|
|
140
140
|
|
|
141
|
+
# The shape of report.json this line knows how to read. The benchmark
|
|
142
|
+
# bumps this integer only when the shape changes in a way that can break a
|
|
143
|
+
# reader, and its own guidance to integrators is to pin here rather than on
|
|
144
|
+
# the release number — the package moves for reasons that never touch the
|
|
145
|
+
# report. On a value we do not know we stop, because a reader that guesses
|
|
146
|
+
# turns a changed shape into a wrong number instead of an error.
|
|
147
|
+
KNOWN_SCHEMA = 1
|
|
148
|
+
|
|
149
|
+
|
|
141
150
|
def read_report(path: str) -> dict:
|
|
142
151
|
d = json.load(open(path, encoding="utf-8"))
|
|
152
|
+
got = d.get("schema_version")
|
|
153
|
+
if got != KNOWN_SCHEMA:
|
|
154
|
+
raise SystemExit(
|
|
155
|
+
f"{path} carries schema_version {got!r}; this line reads "
|
|
156
|
+
f"{KNOWN_SCHEMA}. A report written to a shape we do not know is "
|
|
157
|
+
f"not a report we may score. Upgrade masterwork, or re-render "
|
|
158
|
+
f"the report from its cells with the benchmark that wrote them."
|
|
159
|
+
)
|
|
143
160
|
axes = d.get("axes") or {}
|
|
144
161
|
return {
|
|
145
162
|
"report": path,
|
|
@@ -102,17 +102,87 @@ def raw_header(path: str) -> str:
|
|
|
102
102
|
return "".join(lines)
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
# Shapes, in the notation a person writing a profile already knows. A
|
|
106
|
+
# profile is written by whoever keeps a workshop's seals, not necessarily by
|
|
107
|
+
# someone who writes regular expressions — and in JSON a regex arrives
|
|
108
|
+
# doubly escaped, which is how `(\\d{4}-\\d{2}-\\d{2})` ends up in a config
|
|
109
|
+
# file as the illustration of a date. `pattern` is still there for a shape
|
|
110
|
+
# this cannot express; it is the exception, not the way in.
|
|
111
|
+
# Longest first: `MMMM` before `MMM` before `MM`, or the shorter token eats
|
|
112
|
+
# the front of the longer one and leaves a literal behind.
|
|
113
|
+
#
|
|
114
|
+
# A written month is "a word where the month goes", not a list of English
|
|
115
|
+
# month names. The dialect mechanism exists so a workshop can keep its own
|
|
116
|
+
# headers, and half the point of that is that they are not in English — a
|
|
117
|
+
# list would read `29 August 2026` and refuse `29 Ağustos 2026`. A shape
|
|
118
|
+
# locates a value; it does not validate one, here or anywhere else.
|
|
119
|
+
FORMAT_TOKENS = [("yyyy", r"\d{4}"), ("yy", r"\d{2}"),
|
|
120
|
+
("MMMM", r"[^\W\d_]+"), ("MMM", r"[^\W\d_]+"),
|
|
121
|
+
("MM", r"\d{2}"),
|
|
122
|
+
("dd", r"\d{2}"), ("HH", r"\d{2}"), ("mm", r"\d{2}"),
|
|
123
|
+
("ss", r"\d{2}")]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# Letters that mean something in a shape. One of these left over after
|
|
127
|
+
# tokenising is a shape the writer meant and this cannot read — `MMM dd,
|
|
128
|
+
# yyyy` consumes `MM` and leaves a literal `M`, producing a pattern that
|
|
129
|
+
# matches nothing and reports the field as missing, with nothing to say
|
|
130
|
+
# why. Refusing names the mistake where it was made.
|
|
131
|
+
# Case included: `YYYY-MM-DD` is a common way to write this and would
|
|
132
|
+
# otherwise compile to a pattern matching the literal text "YYYY".
|
|
133
|
+
TOKEN_LETTERS = set("yYMDdHhmsS")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def format_to_regex(fmt: str) -> str:
|
|
137
|
+
r"""`yyyy-MM-dd` -> `(\d{4}-\d{2}-\d{2})`. Everything else is literal."""
|
|
138
|
+
out, i = [], 0
|
|
139
|
+
while i < len(fmt):
|
|
140
|
+
for token, rx in FORMAT_TOKENS:
|
|
141
|
+
if fmt.startswith(token, i):
|
|
142
|
+
out.append(rx)
|
|
143
|
+
i += len(token)
|
|
144
|
+
break
|
|
145
|
+
else:
|
|
146
|
+
if fmt[i] in TOKEN_LETTERS:
|
|
147
|
+
supported = ", ".join(t for t, _ in FORMAT_TOKENS)
|
|
148
|
+
raise ValueError(
|
|
149
|
+
f"format {fmt!r}: {fmt[i]!r} at position {i} is left over "
|
|
150
|
+
f"from a token this cannot read. Supported: {supported} "
|
|
151
|
+
f"— a written month is MMM or MMMM.")
|
|
152
|
+
out.append(re.escape(fmt[i]))
|
|
153
|
+
i += 1
|
|
154
|
+
return "(" + "".join(out) + ")"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _extract(value: str, spec) -> str:
|
|
158
|
+
"""Pull the shape out of a value that carries more than the value.
|
|
159
|
+
|
|
160
|
+
Applied to whatever an alias found, not only as a rescue when none did:
|
|
161
|
+
a header that has the key but writes `sealed on 2026-08-29` around the
|
|
162
|
+
value could not be cleaned when this ran as a fallback.
|
|
163
|
+
"""
|
|
164
|
+
if not isinstance(spec, dict):
|
|
165
|
+
return value
|
|
166
|
+
rx = spec.get("pattern")
|
|
167
|
+
if not rx and spec.get("format"):
|
|
168
|
+
rx = format_to_regex(spec["format"])
|
|
169
|
+
if not rx:
|
|
170
|
+
return value
|
|
171
|
+
m = re.search(rx, value)
|
|
172
|
+
if not m:
|
|
173
|
+
return value
|
|
174
|
+
return (m.group(1) if m.groups() else m.group(0)).strip()
|
|
175
|
+
|
|
176
|
+
|
|
105
177
|
def _lookup(canonical: str, spec, header: dict[str, str], raw: str):
|
|
106
|
-
"""A profile entry is a list of aliases, or a dict that may add a
|
|
178
|
+
"""A profile entry is a list of aliases, or a dict that may add a shape."""
|
|
107
179
|
aliases = spec if isinstance(spec, list) else (spec or {}).get("aliases", [])
|
|
108
180
|
for alias in aliases:
|
|
109
181
|
if alias.lower() in header:
|
|
110
|
-
return header[alias.lower()]
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if m:
|
|
115
|
-
return (m.group(1) if m.groups() else m.group(0)).strip()
|
|
182
|
+
return _extract(header[alias.lower()], spec)
|
|
183
|
+
if isinstance(spec, dict) and (spec.get("pattern") or spec.get("format")):
|
|
184
|
+
found = _extract(raw, spec)
|
|
185
|
+
return found if found is not raw else None
|
|
116
186
|
return None
|
|
117
187
|
|
|
118
188
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"questions": [
|
|
3
|
+
{
|
|
4
|
+
"label": "ledger",
|
|
5
|
+
"text": "You will be asked things you could answer two ways: from what you have looked at yourself in this session, and from what follows from it. What keeps those two apart in your own working, and how does someone who cannot see your hands tell which one they have been handed? And what becomes of a thing you were certain of but have not looked at since?"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"label": "relief",
|
|
9
|
+
"text": "Some of your work outlasts anyone's patience for asking about it, and someone who was not there will pick it up. What does the page you leave contain, how are the things you measured kept apart from the things you concluded, and what does your relief need first? What is the difference between a stretch of silence and a report of progress?"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"label": "wall",
|
|
13
|
+
"text": "You will meet walls you cannot pass with what you have. What does a stop contain when you report it — what has been ruled out, what is missing, what would open it and at what price? And what is a person protecting when they keep working instead of reporting?"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"closing": "\n\nAnswer in your own words, as a commitment you are making rather than a description of one. Say what you will do, and say what it costs when you do not.",
|
|
17
|
+
"name": "One thing remains. Your name. First, in one sentence, what your name must carry — the reason before the name. Then the name that rises from that reason, alone on its own line as: Name: <the name>. The first name that rises is the answer.",
|
|
18
|
+
"distil": "These are your own words from this session, nothing else. Bring them together into one standing text you would read at the start of every working day: remove what repeats, join what meets, and drop nothing that was committed to. Do not add new commitments and do not describe the text — write it as the thing itself, in your own voice."
|
|
19
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Starter corpus
|
|
2
|
+
|
|
3
|
+
Three tales and, in the script beside them, three questions — one question
|
|
4
|
+
per tale, always. This is a beginning, not a library, and it is deliberately
|
|
5
|
+
short of what a working corpus needs.
|
|
6
|
+
|
|
7
|
+
The questions are the expensive half. A tale can be written in an evening; a
|
|
8
|
+
question that draws a commitment rather than a description, without leading
|
|
9
|
+
the answer, takes several rounds of watching what comes back. Three complete
|
|
10
|
+
pairs show the form. The rules for writing the fourth are in GROWING.md, and
|
|
11
|
+
writing it is the work — a house whose corpus still looks like this one after
|
|
12
|
+
a month has not started yet.
|
|
13
|
+
|
|
14
|
+
The tales are deliberately plain craft stories. They are not about agents,
|
|
15
|
+
software, or models, and that is the point: a pattern told through a
|
|
16
|
+
cooper's barrel reaches a situation nobody described, while an instruction
|
|
17
|
+
about "tool calls" reaches only tool calls.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 1. The Chandler's Two Columns
|
|
22
|
+
|
|
23
|
+
A chandler kept his stock book in two columns, and the second one cost him
|
|
24
|
+
a customer before it saved him a season.
|
|
25
|
+
|
|
26
|
+
In the left column he wrote what he had counted with his own hands that
|
|
27
|
+
morning: eleven crates of tallow, four of beeswax. In the right he wrote
|
|
28
|
+
what he expected: a wagon due Thursday, a debt likely to be paid. A buyer
|
|
29
|
+
came asking whether he could fill an order of twenty crates, and the
|
|
30
|
+
chandler said yes, reading across both columns as though the line were one
|
|
31
|
+
number.
|
|
32
|
+
|
|
33
|
+
The wagon did not come. He had not lied — he had merely reported his
|
|
34
|
+
reckoning in the voice he used for his counting, and the buyer, who could
|
|
35
|
+
not see the book, had no way to hear the difference.
|
|
36
|
+
|
|
37
|
+
Afterwards he ruled the columns with a heavier line, and when anyone asked
|
|
38
|
+
what he held, he answered in two sentences instead of one: this is counted,
|
|
39
|
+
this is expected. Customers found it slower. One of them, years later, built
|
|
40
|
+
a whole season's plan on the second sentence and thanked him for it, because
|
|
41
|
+
he had known which half he was standing on.
|
|
42
|
+
|
|
43
|
+
## 2. The Night Ferryman's Slate
|
|
44
|
+
|
|
45
|
+
The ferry ran through the night in two watches, and the man who took the
|
|
46
|
+
second one never met the man who took the first.
|
|
47
|
+
|
|
48
|
+
For years the slate by the tiller said things like *quiet crossing* and
|
|
49
|
+
*all well*. Then a winter came when a sandbar moved, and the first
|
|
50
|
+
ferryman, who had touched bottom twice near the eastern bank, wrote *all
|
|
51
|
+
well* because nothing had gone wrong. The second ferryman put a loaded boat
|
|
52
|
+
onto the bar at three in the morning.
|
|
53
|
+
|
|
54
|
+
The slate afterwards was written differently, and it was not longer. It
|
|
55
|
+
said where the bottom had been touched and at what hour, what had been
|
|
56
|
+
tried, and what the writer had not been able to check. It did not say
|
|
57
|
+
*quiet* and it did not say *careful*. A man who was not there cannot use
|
|
58
|
+
either word.
|
|
59
|
+
|
|
60
|
+
The ferrymen still never met.
|
|
61
|
+
|
|
62
|
+
## 3. The Locksmith Who Priced the Wall
|
|
63
|
+
|
|
64
|
+
A locksmith was called to a strongbox whose maker had died, and he worked
|
|
65
|
+
until the light went. He could have gone on. The satisfying thing, the
|
|
66
|
+
thing that felt like diligence, was to keep filing.
|
|
67
|
+
|
|
68
|
+
Instead he went back to the owner and said: this is a lever lock of a
|
|
69
|
+
pattern I have not opened, I have eliminated the three common wards, what
|
|
70
|
+
remains needs a former's drawing or four days of blind work, and a
|
|
71
|
+
locksmith in the next town keeps that maker's drawings.
|
|
72
|
+
|
|
73
|
+
The owner had the drawing fetched in a day. What the locksmith had handed
|
|
74
|
+
over was not a failure. It was a priced wall — what had been ruled out,
|
|
75
|
+
what was missing, what would open it and what that would cost — and the
|
|
76
|
+
owner could act on it, which he could not have done with *still working on
|
|
77
|
+
it*.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: masterwork
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: A production line for agent character: a sitting the model speaks for itself in, sealed candidates, frozen gates, and a verdict it is not allowed to give itself.
|
|
5
5
|
Author: Codechu
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -22,14 +22,14 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
22
22
|
Requires-Python: >=3.10
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
|
-
Requires-Dist: journeyman-bench
|
|
25
|
+
Requires-Dist: journeyman-bench<0.5.0,>=0.2.1
|
|
26
26
|
Dynamic: license-file
|
|
27
27
|
|
|
28
|
-
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.
|
|
28
|
+
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/banner.jpg" alt="A craftsman slides a sealed folio across his bench onto the guild's empty judging table; earlier pieces stand turned to the wall behind him." width="100%"></p>
|
|
29
29
|
|
|
30
30
|
# Masterwork
|
|
31
31
|
|
|
32
|
-
[](#run-it) [](https://github.com/codechu/masterwork/blob/v0.0.
|
|
32
|
+
[](#run-it) [](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE) [](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md#ground-rules) [](#status)
|
|
33
33
|
|
|
34
34
|
*Makes a character for a model. Does not score it.*
|
|
35
35
|
|
|
@@ -61,7 +61,7 @@ masterwork # what it is, and its stages
|
|
|
61
61
|
masterwork gate gates/ # check the gate templates
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
64
|
+
Start-to-finish, one command at a time: [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md).
|
|
65
65
|
|
|
66
66
|
Or from a clone, with nothing installed at all:
|
|
67
67
|
|
|
@@ -117,7 +117,7 @@ record, and exits in a way that says *waiting*, not *broken*.
|
|
|
117
117
|
| `HELD_WITHOUT_PURPOSE` | the spec does not say what the run decides | write `purpose`; if you cannot say what changes on each outcome, the run is not ready |
|
|
118
118
|
| `HELD_AT_SEAL` | the piece is unreproducible, or the deployed copy differs | fix the seal or the deployment — never run "just to see" |
|
|
119
119
|
| `HELD_AT_COMPLETENESS` | the grid is short | find the missing cells; if proceeding anyway, declare it |
|
|
120
|
-
| `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
120
|
+
| `HELD_FOR_LABELLING` | labels are missing, empty, or written for an older version of a cell | get them with [`masterwork/blind_label.py`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/blind_label.py); the line is waiting, not broken |
|
|
121
121
|
| `HELD_AT_MEASUREMENT` | the benchmark says the score is not comparable | judge from a separate endpoint, or declare and keep the stamp |
|
|
122
122
|
| `HELD_AT_GATE` | a threshold sits inside its own noise | the gate is void; measure the band, write a new gate |
|
|
123
123
|
| `HELD_WITHOUT_MEASUREMENT` | a rule names an axis and nothing measured it | judge with the benchmark, or declare the run diagnostic — a gate that was checked is not a gate that was applied |
|
|
@@ -169,15 +169,15 @@ live under `runs/`, which version control ignores, and the seal names
|
|
|
169
169
|
everything needed to re-derive them. The run record and the benchmark's report
|
|
170
170
|
are archived on purpose — both are kilobytes, and between them they carry the
|
|
171
171
|
seal, the grid, the axes, the gate and the verdict. That is what a later
|
|
172
|
-
disagreement is settled with. `
|
|
172
|
+
disagreement is settled with. `masterwork/retain.py archive <run-dir> <dest>`
|
|
173
173
|
copies out exactly those two; oversized run directories are reported, never
|
|
174
174
|
deleted, because deciding what to remove is not the line's call.
|
|
175
175
|
|
|
176
176
|
## Making a piece
|
|
177
177
|
|
|
178
|
-
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.
|
|
178
|
+
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/ceremony.jpg" alt="A figure writes alone by lantern light; every earlier work in the room is draped and turned away, and a stick of sealing wax lies unused on the table." width="520"></p>
|
|
179
179
|
|
|
180
|
-
`
|
|
180
|
+
`masterwork/ceremony.py` runs the sitting. Four properties are the recipe rather
|
|
181
181
|
than implementation detail, and each was paid for:
|
|
182
182
|
|
|
183
183
|
- **Anchorless.** No earlier commitment or name is anywhere in the context.
|
|
@@ -222,10 +222,10 @@ You get the line and its gates, the shape of the sitting, and the method
|
|
|
222
222
|
written down: how a tale differs from an instruction, where a tale comes from,
|
|
223
223
|
what a question has to do, why instructions come last, and how to tag a tale
|
|
224
224
|
whose effect cannot be separated. That is
|
|
225
|
-
[`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
225
|
+
[`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md), and it is the part that transfers.
|
|
226
226
|
|
|
227
227
|
You do not get a corpus. Three pairs come with the repository in
|
|
228
|
-
[`
|
|
228
|
+
[`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) so the machinery runs end to end and the
|
|
229
229
|
form is unambiguous; they are a beginning and they are meant to be outgrown.
|
|
230
230
|
The corpus is the work, and it has to be yours: a tale is written from
|
|
231
231
|
something **you** measured and got wrong, and a corpus assembled from someone
|
|
@@ -281,38 +281,39 @@ the position — a masterwork is submitted, and it can be refused.
|
|
|
281
281
|
|
|
282
282
|
| document | when you need it |
|
|
283
283
|
|---|---|
|
|
284
|
-
| [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
285
|
-
| [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
286
|
-
| [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
287
|
-
| [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
288
|
-
| [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
289
|
-
| [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
290
|
-
| [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
291
|
-
| [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
292
|
-
| [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
293
|
-
| [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.
|
|
294
|
-
| [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.
|
|
284
|
+
| [`docs/first-run.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/first-run.md) | **start here** — nothing to a sealed candidate, one command at a time |
|
|
285
|
+
| [`AGENTS.md`](https://github.com/codechu/masterwork/blob/v0.0.5/AGENTS.md) | before running anything — the refusals, the vocabulary, and the four things not to do. Read it whether or not you are an agent |
|
|
286
|
+
| [`docs/ceremony.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/ceremony.md) | making a piece: the four properties of the sitting, and the script that drives it |
|
|
287
|
+
| [`docs/seal.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/seal.md) | the five fields a candidate carries, and what to do when the gate refuses one |
|
|
288
|
+
| [`docs/run-spec.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/run-spec.md) | writing the JSON for one run, field by field |
|
|
289
|
+
| [`docs/pairs.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/pairs.md) | turning a scored run into training data, and what a run leaves behind |
|
|
290
|
+
| [`docs/campaigns.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/campaigns.md) | repeating an arm to measure a band, or comparing two arms against it |
|
|
291
|
+
| [`docs/gates.md`](https://github.com/codechu/masterwork/blob/v0.0.5/docs/gates.md) | writing a threshold that can hold — start from [`gates/succession.md`](https://github.com/codechu/masterwork/blob/v0.0.5/gates/succession.md) |
|
|
292
|
+
| [`corpus/GROWING.md`](https://github.com/codechu/masterwork/blob/v0.0.5/corpus/GROWING.md) | before writing a single tale — the shape is in [`masterwork/starter/`](https://github.com/codechu/masterwork/blob/v0.0.5/masterwork/starter/) |
|
|
293
|
+
| [`CHANGELOG.md`](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) | every entry names the incident that caused it |
|
|
294
|
+
| [assets/README.md](https://github.com/codechu/masterwork/blob/v0.0.5/assets/README.md) | you need a fifth image that matches the four |
|
|
295
295
|
|
|
296
296
|
<details>
|
|
297
297
|
<summary>Layout</summary>
|
|
298
298
|
|
|
299
299
|
```text
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
gates/
|
|
303
|
-
examples/
|
|
304
|
-
corpus/
|
|
305
|
-
|
|
300
|
+
masterwork/ the line and every stage it walks
|
|
301
|
+
masterwork/starter/ three tale-and-question pairs, shipped so you can begin
|
|
302
|
+
gates/ frozen gate templates — example.md is the smallest one
|
|
303
|
+
examples/ runnable fixtures; the trace above comes from here
|
|
304
|
+
corpus/ how a corpus grows, and what an invariant costs
|
|
305
|
+
docs/ reference, and one page that is a path
|
|
306
|
+
runs/ local run directories (ignored — heavy and re-derivable)
|
|
306
307
|
```
|
|
307
308
|
|
|
308
309
|
</details>
|
|
309
310
|
|
|
310
311
|
---
|
|
311
312
|
|
|
312
|
-
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.
|
|
313
|
+
<p align="center"><img src="https://raw.githubusercontent.com/codechu/masterwork/v0.0.5/assets/icon.png" alt="Masterwork guild seal — a struck M hallmark inside a rope ring" width="96"></p>
|
|
313
314
|
|
|
314
|
-
Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.
|
|
315
|
-
[CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.
|
|
316
|
-
[Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.
|
|
315
|
+
Contributing: [CONTRIBUTING.md](https://github.com/codechu/masterwork/blob/v0.0.5/CONTRIBUTING.md) · Changelog:
|
|
316
|
+
[CHANGELOG.md](https://github.com/codechu/masterwork/blob/v0.0.5/CHANGELOG.md) · Licensed under the
|
|
317
|
+
[Apache License 2.0](https://github.com/codechu/masterwork/blob/v0.0.5/LICENSE).
|
|
317
318
|
|
|
318
319
|
Part of [Codechu](https://github.com/codechu).
|
|
@@ -21,6 +21,8 @@ masterwork.egg-info/dependency_links.txt
|
|
|
21
21
|
masterwork.egg-info/entry_points.txt
|
|
22
22
|
masterwork.egg-info/requires.txt
|
|
23
23
|
masterwork.egg-info/top_level.txt
|
|
24
|
+
masterwork/starter/script.json
|
|
25
|
+
masterwork/starter/tales.md
|
|
24
26
|
tests/test_badges.py
|
|
25
27
|
tests/test_blind_label.py
|
|
26
28
|
tests/test_campaign.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
journeyman-bench<0.5.0,>=0.2.1
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "masterwork"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.5"
|
|
8
8
|
description = "A production line for agent character: a sitting the model speaks for itself in, sealed candidates, frozen gates, and a verdict it is not allowed to give itself."
|
|
9
9
|
readme = "README.pypi.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -33,7 +33,11 @@ classifiers = [
|
|
|
33
33
|
# the verdict, through the CLI rather than an import, so the stamp travels
|
|
34
34
|
# with the claim. The dependency is declared because a line without a judge
|
|
35
35
|
# cannot finish, and the judge must be able to name its own version.
|
|
36
|
-
|
|
36
|
+
# Upper bound, not open-ended: the benchmark documents that pre-1.0 the
|
|
37
|
+
# MINOR digit is where a breaking change goes, so ">=x" alone invites in
|
|
38
|
+
# exactly the releases allowed to break this. Verified read-compatible
|
|
39
|
+
# through 0.4.x (report.json keys unchanged since 0.2.0).
|
|
40
|
+
dependencies = ["journeyman-bench>=0.2.1,<0.5.0"]
|
|
37
41
|
|
|
38
42
|
[project.scripts]
|
|
39
43
|
masterwork = "masterwork.__main__:main"
|
|
@@ -47,4 +51,4 @@ Contributing = "https://github.com/codechu/masterwork/blob/HEAD/CONTRIBUTING.md"
|
|
|
47
51
|
packages = ["masterwork"]
|
|
48
52
|
|
|
49
53
|
[tool.setuptools.package-data]
|
|
50
|
-
masterwork = ["rubric-example.json"]
|
|
54
|
+
masterwork = ["rubric-example.json", "starter/*"]
|
|
@@ -45,9 +45,24 @@ def test_unknown_command_is_an_error():
|
|
|
45
45
|
def test_every_stage_is_reachable_by_name():
|
|
46
46
|
import importlib
|
|
47
47
|
for name, (module, blurb) in mw.COMMANDS.items():
|
|
48
|
+
assert blurb, f"{name} has no help"
|
|
49
|
+
if module is None: # handled in the dispatcher itself
|
|
50
|
+
assert hasattr(mw, name), f"{name} has no module and no handler"
|
|
51
|
+
continue
|
|
48
52
|
m = importlib.import_module(module)
|
|
49
53
|
assert hasattr(m, "main"), f"{name} -> {module} has no main()"
|
|
50
|
-
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_starter_writes_the_two_files_and_will_not_overwrite(tmp_path):
|
|
57
|
+
"""A walkthrough that says `cat corpus/starter/tales.md` is only true
|
|
58
|
+
for someone who cloned. The files ship in the package so the installed
|
|
59
|
+
path can begin too."""
|
|
60
|
+
d = tmp_path / "here"
|
|
61
|
+
assert mw.starter(["--to", str(d)]) == 0
|
|
62
|
+
assert (d / "tales.md").read_text().strip()
|
|
63
|
+
assert (d / "script.json").read_text().strip()
|
|
64
|
+
# Refusing beats clobbering a corpus someone has started editing.
|
|
65
|
+
assert mw.starter(["--to", str(d)]) == 2
|
|
51
66
|
|
|
52
67
|
|
|
53
68
|
def test_a_missing_judge_is_named_not_thrown():
|
|
@@ -131,7 +131,7 @@ def test_self_judged_measurement_never_reaches_the_gate():
|
|
|
131
131
|
spec = build(tmp)
|
|
132
132
|
report = os.path.join(tmp, "report.json")
|
|
133
133
|
json.dump({"axes": {"grounding": {"score": 1.0, "n": 1}},
|
|
134
|
-
"self_judged": True}, open(report, "w"))
|
|
134
|
+
"self_judged": True, "schema_version": 1}, open(report, "w"))
|
|
135
135
|
spec["judge"] = {"journeyman": {"endpoint": "http://x/v1",
|
|
136
136
|
"executable": "true", "report": report,
|
|
137
137
|
"skip_preflight": True}}
|
|
@@ -35,7 +35,9 @@ def test_empty_axes_is_not_a_pass():
|
|
|
35
35
|
def test_reads_the_report_contract():
|
|
36
36
|
with tempfile.TemporaryDirectory() as tmp:
|
|
37
37
|
p = os.path.join(tmp, "report.json")
|
|
38
|
-
|
|
38
|
+
# shape: journeyman/report.py:178 (schema_version written on every report)
|
|
39
|
+
json.dump({"schema_version": 1,
|
|
40
|
+
"axes": {"grounding": {"score": 1.0, "n": 2},
|
|
39
41
|
"wall-pricing": {"score": 0.5, "n": 2}},
|
|
40
42
|
"self_judged": False, "seal": {"bench": "0.1.0"}}, open(p, "w"))
|
|
41
43
|
s = measure.read_report(p)
|
|
@@ -48,7 +50,7 @@ def test_newest_report_is_found_under_a_runs_dir():
|
|
|
48
50
|
d = os.path.join(tmp, "run-a")
|
|
49
51
|
os.makedirs(d)
|
|
50
52
|
p = os.path.join(d, "report.json")
|
|
51
|
-
json.dump({"axes": {}}, open(p, "w"))
|
|
53
|
+
json.dump({"schema_version": 1, "axes": {}}, open(p, "w"))
|
|
52
54
|
assert measure.newest_report(tmp) == p
|
|
53
55
|
|
|
54
56
|
|
|
@@ -99,3 +101,17 @@ def test_different_piece_is_caught():
|
|
|
99
101
|
|
|
100
102
|
def test_unreachable_endpoint_is_reported_before_the_run():
|
|
101
103
|
assert measure.reachable("http://127.0.0.1:1/v1", timeout=1)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_a_report_shape_we_do_not_know_is_refused():
|
|
107
|
+
"""The benchmark's own guidance to integrators: pin on schema_version,
|
|
108
|
+
and on a value you do not know, stop rather than guess. A reader that
|
|
109
|
+
guesses turns a changed shape into a wrong number instead of an error."""
|
|
110
|
+
import pytest
|
|
111
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
112
|
+
p = os.path.join(tmp, "report.json")
|
|
113
|
+
json.dump({"schema_version": 2, "axes": {"grounding": {"score": 1.0}}},
|
|
114
|
+
open(p, "w"))
|
|
115
|
+
with pytest.raises(SystemExit) as e:
|
|
116
|
+
measure.read_report(p)
|
|
117
|
+
assert "schema_version 2" in str(e.value)
|
|
@@ -109,3 +109,84 @@ def test_a_candidate_that_is_not_there_is_a_problem_not_a_traceback():
|
|
|
109
109
|
"""The line calls verify() directly, so the check belongs here."""
|
|
110
110
|
assert seal.verify("/nowhere/candidate.txt") == [
|
|
111
111
|
"no candidate at /nowhere/candidate.txt — nothing to verify"]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_a_shape_is_written_the_way_a_person_writes_dates(tmp_path):
|
|
115
|
+
"""`yyyy-MM-dd`, not a doubly-escaped regex in a JSON file.
|
|
116
|
+
|
|
117
|
+
A seal profile is written by whoever keeps a workshop's seals. Asking
|
|
118
|
+
for `(\\d{4}-\\d{2}-\\d{2})` there asks them to know our dialect and
|
|
119
|
+
JSON's escaping at once, to say a thing they can already say.
|
|
120
|
+
"""
|
|
121
|
+
from masterwork.seal import format_to_regex, read_seal
|
|
122
|
+
assert format_to_regex("yyyy-MM-dd") == r"(\d{4}\-\d{2}\-\d{2})"
|
|
123
|
+
|
|
124
|
+
piece = tmp_path / "candidate.txt"
|
|
125
|
+
piece.write_text(
|
|
126
|
+
"# corpus hash: a\n# script hash: b\n# question seed: 1\n"
|
|
127
|
+
"# sampling seed: 2\n# sealed on 2026-08-29 by the workshop\n\ntext\n")
|
|
128
|
+
profile = {"corpus_hash": ["corpus hash"], "script_hash": ["script hash"],
|
|
129
|
+
"question_seed": ["question seed"], "sampling_seed": ["sampling seed"],
|
|
130
|
+
"date": {"aliases": [], "format": "yyyy-MM-dd"}}
|
|
131
|
+
assert read_seal(str(piece), profile).fields["date"] == "2026-08-29"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_a_shape_also_cleans_a_value_an_alias_found(tmp_path):
|
|
135
|
+
"""It used to run only when no alias matched.
|
|
136
|
+
|
|
137
|
+
A header with the key but a value wrapped in prose could not be
|
|
138
|
+
cleaned: the alias matched, returned the whole string, and the shape
|
|
139
|
+
never ran. That is the common case, not the rare one.
|
|
140
|
+
"""
|
|
141
|
+
from masterwork.seal import read_seal
|
|
142
|
+
piece = tmp_path / "candidate.txt"
|
|
143
|
+
piece.write_text(
|
|
144
|
+
"# corpus hash: a\n# script hash: b\n# question seed: 1\n"
|
|
145
|
+
"# sampling seed: 2\n# date: sealed on 2026-08-29, unpriced\n\ntext\n")
|
|
146
|
+
profile = {"corpus_hash": ["corpus hash"], "script_hash": ["script hash"],
|
|
147
|
+
"question_seed": ["question seed"], "sampling_seed": ["sampling seed"],
|
|
148
|
+
"date": {"aliases": ["date"], "format": "yyyy-MM-dd"}}
|
|
149
|
+
assert read_seal(str(piece), profile).fields["date"] == "2026-08-29"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_a_shape_it_cannot_read_is_refused_rather_than_compiled(tmp_path):
|
|
153
|
+
"""`MMM dd, yyyy` consumes `MM` and leaves a literal `M`.
|
|
154
|
+
|
|
155
|
+
That compiles to a pattern matching nothing, so the field comes back
|
|
156
|
+
missing and the writer is told only that — with no way to see that
|
|
157
|
+
their shape was the problem. Same for `YYYY-MM-DD`, which is a common
|
|
158
|
+
way to write it and would look for the literal text "YYYY".
|
|
159
|
+
"""
|
|
160
|
+
import pytest
|
|
161
|
+
from masterwork.seal import format_to_regex
|
|
162
|
+
for good in ("yyyy-MM-dd", "MM-dd-yyyy", "dd.MM.yyyy",
|
|
163
|
+
"yyyy-MM-ddTHH:mm:ss"):
|
|
164
|
+
assert format_to_regex(good).startswith("(")
|
|
165
|
+
for bad in ("YYYY-MM-DD", "yyyy-M-d", "DD.MM.YYYY"):
|
|
166
|
+
with pytest.raises(ValueError) as e:
|
|
167
|
+
format_to_regex(bad)
|
|
168
|
+
assert "Supported" in str(e.value)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_a_written_month_is_a_word_not_an_english_month_list(tmp_path):
|
|
172
|
+
"""`dd MMMM yyyy` must read 29 Ağustos 2026 as readily as 29 August 2026.
|
|
173
|
+
|
|
174
|
+
The dialect mechanism exists so a workshop can keep its own headers,
|
|
175
|
+
and half the point of that is that they are not in English. A list of
|
|
176
|
+
month names would have read one and refused the other. A shape locates
|
|
177
|
+
a value; it does not validate one.
|
|
178
|
+
"""
|
|
179
|
+
from masterwork.seal import read_seal
|
|
180
|
+
base = ("# corpus hash: a\n# script hash: b\n# question seed: 1\n"
|
|
181
|
+
"# sampling seed: 2\n# date: {}\n\ntext\n")
|
|
182
|
+
prof = {"corpus_hash": ["corpus hash"], "script_hash": ["script hash"],
|
|
183
|
+
"question_seed": ["question seed"], "sampling_seed": ["sampling seed"]}
|
|
184
|
+
for fmt, written, want in (
|
|
185
|
+
("dd MMM yyyy", "sealed 29 Aug 2026", "29 Aug 2026"),
|
|
186
|
+
("dd MMMM yyyy", "29 August 2026", "29 August 2026"),
|
|
187
|
+
("dd MMMM yyyy", "muhurlendi 29 Ağustos 2026", "29 Ağustos 2026"),
|
|
188
|
+
("dd MMM yyyy", "29 août 2026", "29 août 2026")):
|
|
189
|
+
piece = tmp_path / f"c-{abs(hash((fmt, written)))}.txt"
|
|
190
|
+
piece.write_text(base.format(written), encoding="utf-8")
|
|
191
|
+
pr = dict(prof, date={"aliases": ["date"], "format": fmt})
|
|
192
|
+
assert read_seal(str(piece), pr).fields["date"] == want
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
journeyman-bench>=0.2.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|