@supersuit/hyperspec 0.7.0 → 0.8.0
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.
- package/CHANGELOG.md +36 -0
- package/README.md +34 -8
- package/SPEC.md +1 -1
- package/WRITING.md +140 -13
- package/bin/hyperspec.mjs +5 -4
- package/examples/writing/course/claims.jsonl +0 -0
- package/examples/writing/course/goldens/lesson.md +1 -0
- package/examples/writing/course/materials/brief.md +9 -0
- package/examples/writing/course/materials/brief.md.segments.jsonl +6 -0
- package/examples/writing/course/outline.md +11 -0
- package/examples/writing/course/part-1.md +47 -0
- package/examples/writing/course/part-2.md +40 -0
- package/examples/writing/course/runs.jsonl +0 -0
- package/examples/writing/course.hyperspec.md +205 -0
- package/package.json +1 -1
- package/src/check.mjs +25 -7
- package/src/sequence-draft.mjs +75 -0
- package/src/stations/index.mjs +3 -1
- package/src/stations/links.mjs +11 -3
- package/src/stations/sequence.mjs +275 -0
- package/src/writing-fields.mjs +34 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0 (2026-09-29)
|
|
4
|
+
|
|
5
|
+
A work read in order can now be checked as one. Every station so far held ONE piece to its spec; a
|
|
6
|
+
course, a primer or a textbook makes promises ACROSS its pieces (Lesson 5 is written for someone who
|
|
7
|
+
has read Lessons 1 to 4 and nothing else), and nothing checked them. Declare `writing.form.sequence`
|
|
8
|
+
and the new `sequence` station does, deterministically, on every `check`. Promoted from a checker
|
|
9
|
+
written for one book, so every sequential work gets it by construction.
|
|
10
|
+
|
|
11
|
+
- `writing.form.sequence`, optional, every key optional: `unit` (the heading word, default
|
|
12
|
+
`Lesson`), `files` (the work's files in reading order; a `*` in a file name matches, sorted by
|
|
13
|
+
number), `sections` (default "After this lesson you can", "New terms", "Try this"),
|
|
14
|
+
`terms_section` (default "New terms"), `outline` (numbered items promising `*Terms: a, b.*`),
|
|
15
|
+
`knows` (words a lesson may use before one defines them, beside `audience.knows`) and `teaser`
|
|
16
|
+
(default "Next,": a closing line naming what is coming). Lint refuses a key that is present and
|
|
17
|
+
hollow under test 1, and a `files` entry matching nothing or an `outline` that is not a file under
|
|
18
|
+
test 6.
|
|
19
|
+
- The `sequence` station, run last: every unit carries its sections; every term is defined in
|
|
20
|
+
exactly one unit; no unit uses a term before the unit that defines it (code, the unit's own terms
|
|
21
|
+
section and its closing teaser are not uses, and a `(from Lesson N)` reminder defines nothing);
|
|
22
|
+
each unit defines what the outline promises, for the units the draft holds; unit numbers increase;
|
|
23
|
+
a pointer to a later unit is a warning. Findings: `station-sequence-no-units`, `-numbering`,
|
|
24
|
+
`-missing-section`, `-defined-twice`, `-used-before-defined`, `-outline-unreadable`,
|
|
25
|
+
`-outline-unkept` (fail) and `-forward-pointer` (warn). A spec with no `sequence` skips it.
|
|
26
|
+
- `hyperspec check <spec>` needs no `--draft` when the spec lists `sequence.files`: the files,
|
|
27
|
+
joined in order with each one's frontmatter blanked, are the draft, and every station reads them.
|
|
28
|
+
A finding names the file and its own line (`(course/part-1.md line 25)`; `file` and `line` in
|
|
29
|
+
`--json`), a relative link resolves beside the file that holds it, and the ledger keys the run by
|
|
30
|
+
the `files` entry as written, so the work keeps one history as parts are added. `--draft` still
|
|
31
|
+
names one file, which may hold every lesson.
|
|
32
|
+
- A third worked example, `examples/writing/course.hyperspec.md`: four lessons in two part files
|
|
33
|
+
with an outline, passing every station with two forward-pointer warnings.
|
|
34
|
+
|
|
35
|
+
**Behavior change:** `check` runs eight stations, so `--json` and the ledger's `stations` carry
|
|
36
|
+
`sequence` (as `skip` for every spec without a sequence). No other station, finding id or schema
|
|
37
|
+
field changed, and a 0.7 ledger's verdicts read as they did.
|
|
38
|
+
|
|
3
39
|
## 0.7.0 (2026-09-29)
|
|
4
40
|
|
|
5
41
|
A spec can now have its judgments made and recorded. `check` covers what a function of the spec
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ improvement ledger. Every test is defined in [SPEC.md](SPEC.md).
|
|
|
28
28
|
| `hyperspec segments init <material> --id <mid> [--out F] [--by paragraph\|sentence]` | Split a material into segments to label. Refuses to overwrite an existing file. |
|
|
29
29
|
| `hyperspec dna init <scope-dir> --writer W --form F --audience A --purpose P` | Start a writer-DNA scope folder. Refuses to overwrite an existing `scope.md`. |
|
|
30
30
|
| `hyperspec dna measure <scope-dir>` | Check every golden in a scope and write its measured features. |
|
|
31
|
-
| `hyperspec check <spec> --draft <file> [--only a,b]` | Run a writing spec's deterministic stations against a draft. |
|
|
31
|
+
| `hyperspec check <spec> [--draft <file>] [--only a,b]` | Run a writing spec's deterministic stations against a draft, or, for a sequential work, against its files in reading order. |
|
|
32
32
|
| `hyperspec judge prepare <spec> --draft <file> --out <dir> [--only a,b] [--force]` | Write one packet per judgment station, for an outside judge to fill. |
|
|
33
33
|
| `hyperspec judge record <packet> --verdict <file>` | Check a judge's verdict against its packet, derive the station's status, and record it. |
|
|
34
34
|
| `hyperspec learn prepare <spec> --first <draft> --approved <draft> --out <dir> [--force]` | Write the edits between a first draft and the approved one, for a judge to classify by spec block. |
|
|
@@ -145,9 +145,10 @@ npx @supersuit/hyperspec init story.hyperspec.md --profile writing --form "short
|
|
|
145
145
|
|
|
146
146
|
The skeleton fails until every placeholder is real and its four open questions (whose voice,
|
|
147
147
|
who speaks, who reads, what changes) are answered. The blocks, every field, and which test
|
|
148
|
-
each rule reports under are in [WRITING.md](WRITING.md).
|
|
149
|
-
nothing to warn ship in `examples/writing/`: an essay for new managers,
|
|
150
|
-
two characters whose voices a judge can tell apart. Each comes with a
|
|
148
|
+
each rule reports under are in [WRITING.md](WRITING.md). Three complete specs that pass with
|
|
149
|
+
nothing to warn ship in `examples/writing/`: an essay for new managers, a short story with
|
|
150
|
+
two characters whose voices a judge can tell apart, and a four-lesson course. Each comes with a
|
|
151
|
+
draft written to it.
|
|
151
152
|
|
|
152
153
|
### Marking materials
|
|
153
154
|
|
|
@@ -193,23 +194,48 @@ it did in 0.4. The folder shape, every feature, and every finding are in
|
|
|
193
194
|
|
|
194
195
|
### Checking a draft
|
|
195
196
|
|
|
196
|
-
Once a draft exists, `check` holds it to its spec with
|
|
197
|
+
Once a draft exists, `check` holds it to its spec with eight stations, none of which calls a
|
|
197
198
|
model or touches the network: `form` (length and required parts), `terms` (every word in the new
|
|
198
199
|
optional `writing.audience.terms` is defined where it first appears), `claims` (the claims
|
|
199
200
|
ledger still matches the draft, and every claim has a source), `quotes` (in nonfiction, every quotation of four
|
|
200
201
|
words or more is word for word in a marked quote), `private` (no run of eight words from a
|
|
201
|
-
private segment), `dna` (the draft's measured style beside its scope's, as warnings)
|
|
202
|
-
(well-formed, and relative links resolve).
|
|
202
|
+
private segment), `dna` (the draft's measured style beside its scope's, as warnings), `links`
|
|
203
|
+
(well-formed, and relative links resolve) and `sequence` (for a work read in order; see below).
|
|
203
204
|
|
|
204
205
|
```bash
|
|
205
206
|
npx @supersuit/hyperspec check essay.hyperspec.md --draft essay/draft.md
|
|
206
207
|
```
|
|
207
208
|
|
|
208
209
|
It lints the spec first, prints each station's pass, fail or skip, and appends one line to the
|
|
209
|
-
spec's runs ledger with a verdict.
|
|
210
|
+
spec's runs ledger with a verdict. Every example ships a draft that passes. What each station
|
|
210
211
|
checks and cannot check, and every finding, are in
|
|
211
212
|
[WRITING.md](WRITING.md#checking-a-draft).
|
|
212
213
|
|
|
214
|
+
### Sequential works
|
|
215
|
+
|
|
216
|
+
A course, a primer or a textbook promises something no single piece can check: Lesson 5 uses only
|
|
217
|
+
words Lessons 1 to 4 defined. Add `sequence:` to `writing.form` and the `sequence` station checks
|
|
218
|
+
it across the whole work: every lesson carries its sections ("After this lesson you can", "New
|
|
219
|
+
terms", "Try this" by default), every term is defined in exactly one lesson, no lesson uses a term
|
|
220
|
+
before the lesson that defines it (code, the part's closing teaser and words the reader already
|
|
221
|
+
knows are exempt), each lesson defines the terms the outline promises, and a pointer to a later
|
|
222
|
+
lesson is a warning. List the work's files and `check` needs no `--draft`:
|
|
223
|
+
|
|
224
|
+
```yaml
|
|
225
|
+
sequence:
|
|
226
|
+
files:
|
|
227
|
+
- course/part-*.md
|
|
228
|
+
outline: course/outline.md
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
npx @supersuit/hyperspec check course.hyperspec.md
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The parts are read in order, a finding names the part and its line, and a new part matching the
|
|
236
|
+
pattern is picked up without touching the spec. Every key, how a lesson is read, and every finding
|
|
237
|
+
are in [WRITING.md](WRITING.md#sequential-works).
|
|
238
|
+
|
|
213
239
|
### Judging a draft and learning from edits
|
|
214
240
|
|
|
215
241
|
The rest of a spec's checks are judgments: whether each goal condition holds, where a reader gets
|
package/SPEC.md
CHANGED
|
@@ -109,7 +109,7 @@ improvement:
|
|
|
109
109
|
|
|
110
110
|
A person writing for another person leaves most of the specification unsaid, because the other person fills the gaps from shared context. An agent has none of that context, so it fills every gap with the average, and the average is what reads as middling. Hyperspecification is writing down the gaps. It is a level of detail that would feel like overkill between two people and is exactly enough for an agent: every decision the agent would otherwise guess is either decided, delegated with the rule for deciding it, or marked open, so the work stops instead of guessing.
|
|
111
111
|
|
|
112
|
-
**Version 0.
|
|
112
|
+
**Version 0.8.0** (2026-09-29)
|
|
113
113
|
|
|
114
114
|
## What makes a spec a hyperspec
|
|
115
115
|
|
package/WRITING.md
CHANGED
|
@@ -245,6 +245,10 @@ writing:
|
|
|
245
245
|
- a close
|
|
246
246
|
stations: # may be empty
|
|
247
247
|
- the three questions render as a numbered list
|
|
248
|
+
sequence: # optional: a work read in order; see Sequential works
|
|
249
|
+
files: # optional: its files in reading order; a * in a file name matches
|
|
250
|
+
- course/part-*.md
|
|
251
|
+
outline: course/outline.md # optional: the outline that promises each unit's terms
|
|
248
252
|
check:
|
|
249
253
|
station: structure and length check
|
|
250
254
|
source: form decision
|
|
@@ -337,12 +341,12 @@ Each row lists what the writing profile adds to that test. The core conditions i
|
|
|
337
341
|
|
|
338
342
|
| Test | A writing spec fails it when |
|
|
339
343
|
|---|---|
|
|
340
|
-
| 1 every decision is accounted for | a required block is missing and not deferred; a required field is missing; a closed-set value is outside its set (`trust`, `reader`, `change.kind`, the shape of `identity`, `unsourced_claim`); `identity: character:<id>` names a character that is not in `writing.characters`; `fiction` is present and is anything other than `true` or `false`; two materials, two spine claims or two characters share an id; `form.length.min` or `max` is not a whole number of at least 1, or `min` is greater than `max`; `spine.claims` has fewer than three or more than seven distinct claims; a character has no knowledge entry, or an entry lacks `by` or `knows`; a material has no text, or no `segments` field, or its segments file is missing, malformed, labels a segment outside the seven (`unlabeled` included), repeats a segment id, or has segments that overlap or leave text uncovered; `dna.scope_dir` is present and is a placeholder; with `dna.scope_dir`, its `scope.md` is missing, unreadable or lacks a field, its writer, form, audience or purpose differs from the spec's, or its `goldens/` folder is missing or empty, or holds a golden that cannot be read, whose frontmatter never closes, or that has no passage; `audience.terms`, when present, holds a non-string entry or has no real entries at all. A `stance` outside the four is a warning, and so is `unsourced_claim: warn` |
|
|
344
|
+
| 1 every decision is accounted for | a required block is missing and not deferred; a required field is missing; a closed-set value is outside its set (`trust`, `reader`, `change.kind`, the shape of `identity`, `unsourced_claim`); `identity: character:<id>` names a character that is not in `writing.characters`; `fiction` is present and is anything other than `true` or `false`; two materials, two spine claims or two characters share an id; `form.length.min` or `max` is not a whole number of at least 1, or `min` is greater than `max`; `spine.claims` has fewer than three or more than seven distinct claims; a character has no knowledge entry, or an entry lacks `by` or `knows`; a material has no text, or no `segments` field, or its segments file is missing, malformed, labels a segment outside the seven (`unlabeled` included), repeats a segment id, or has segments that overlap or leave text uncovered; `dna.scope_dir` is present and is a placeholder; with `dna.scope_dir`, its `scope.md` is missing, unreadable or lacks a field, its writer, form, audience or purpose differs from the spec's, or its `goldens/` folder is missing or empty, or holds a golden that cannot be read, whose frontmatter never closes, or that has no passage; `audience.terms`, when present, holds a non-string entry or has no real entries at all; `form.sequence` is present and is not a map, or has `unit`, `terms_section`, `teaser` or `outline` empty or a placeholder, or has `files`, `sections` or `knows` that is not a list of real entries. A `stance` outside the four is a warning, and so is `unsourced_claim: warn` |
|
|
341
345
|
| 2 every requirement can fail | `goal.conditions` lists fewer than five or more than ten distinct ids, lists an id twice, or names an id that is not a top-level requirement |
|
|
342
346
|
| 3 every requirement names its check | a block or a character has no `check` with a `station` or a `rubric` |
|
|
343
347
|
| 4 every field says where it came from and who wrote it | a block or a character has no `source` or no `author`; a spine claim names no materials, or names a material id that is not in `materials.items`, or a segment that is not in that material's segments file; a segment's text does not match its material word for word; a material changed after it was marked; a claim segment has no `source` and no `own`, a story no `teller`, a quote no `speaker`; with `dna.scope_dir`, a golden in the scope has no `approved_by`, an approver that starts `agent:`, or no `source` |
|
|
344
348
|
| 5 negative space is specified | `persona.will_not_say` is empty; `persona.facts_from` is anything other than `sources`; a spine claim cites a `private` or a `question` segment; with `dna.scope_dir`, a golden the spec lists is not one of the scope's goldens (it lives outside the scope's `goldens/` folder, is a symlink that resolves outside it, or sits in a subfolder, is `README.md` or is not a `.md` file), or the scope's `goldens/` folder resolves outside the scope |
|
|
345
|
-
| 6 examples outrank adjectives | a golden has no `why`; a material, `dna.rules`, golden
|
|
349
|
+
| 6 examples outrank adjectives | a golden has no `why`; a material, `dna.rules`, golden, character `entity` or `form.sequence.outline` path does not exist or is not a file; a `form.sequence.files` entry matches no file; a character has no golden lines or no rejected lines, or has the same line in both (compared trimmed and case-folded); with `dna.scope_dir`, a golden in the scope has no `why`, or the scope's `features.json` is missing or is not what `dna measure` would write now |
|
|
346
350
|
| 7 a stranger can resume it | `writing.progress` exists. An unknown `profile:` is a warning |
|
|
347
351
|
| 8 its adopters can push back on it | nothing further; the core rule applies |
|
|
348
352
|
| 9 it improves itself | nothing further; the core rule applies |
|
|
@@ -810,7 +814,7 @@ npx @supersuit/hyperspec check essay.hyperspec.md --draft essay/draft.md
|
|
|
810
814
|
|
|
811
815
|
It lints the spec first. A spec that fails lint, or is blocked on an open decision, runs no
|
|
812
816
|
station and exits with lint's own code, because a draft cannot be checked against a spec that is
|
|
813
|
-
not ready. Then it runs
|
|
817
|
+
not ready. Then it runs eight stations in a fixed order and prints one line for each: `pass`,
|
|
814
818
|
`fail` with its findings, or `skip` with the reason. A warning prints under its station and never
|
|
815
819
|
fails it. This is the essay example's draft:
|
|
816
820
|
|
|
@@ -824,6 +828,7 @@ dna: pass
|
|
|
824
828
|
warn [station-dna-drift] first_person_singular_rate is 22.892 in the draft; the scope's goldens measure 0, band 0 to 5
|
|
825
829
|
fix: Bring first_person_singular_rate back inside the band, or, if the scope no longer describes this writer, re-measure it with better goldens.
|
|
826
830
|
links: pass
|
|
831
|
+
sequence: skip (the spec declares no writing.form.sequence)
|
|
827
832
|
verdict: one-shot
|
|
828
833
|
```
|
|
829
834
|
|
|
@@ -832,10 +837,13 @@ marked partial (see [The runs ledger](#the-runs-ledger)). `--json` prints the wh
|
|
|
832
837
|
finding included; a spec that is not ready prints lint's result with `lintBlocked: true` instead,
|
|
833
838
|
and a usage error prints `{ "spec", "draft", "error" }`. A finding names the draft line it points
|
|
834
839
|
at where there is one, quotes at most 80 characters of the draft, and never prints an absolute
|
|
835
|
-
path. A UTF-8 byte order mark at the start of the draft is ignored.
|
|
840
|
+
path. A UTF-8 byte order mark at the start of the draft is ignored. A spec that lists
|
|
841
|
+
`form.sequence.files` needs no `--draft`: its files, joined in reading order, are the draft, and a
|
|
842
|
+
finding names the file and its own line in it (see [Sequential works](#sequential-works)).
|
|
836
843
|
|
|
837
844
|
Exit codes: **0** every station that ran passed (a skip or a warning does not fail it); **1** a
|
|
838
|
-
station failed; **2** usage: no spec path, no `--draft
|
|
845
|
+
station failed; **2** usage: no spec path, no `--draft` for a spec that lists no
|
|
846
|
+
`form.sequence.files`, a draft that cannot be read, a spec
|
|
839
847
|
without `profile: writing`, or an `--only` that names no known station; and lint's own **1** or
|
|
840
848
|
**3** when the spec is not ready.
|
|
841
849
|
|
|
@@ -973,6 +981,25 @@ the network, so it cannot tell you a URL is live.
|
|
|
973
981
|
| `station-links-root-relative` | warn | a link starting with `/`, which cannot be resolved without the site |
|
|
974
982
|
| `station-links-undefined-reference` | fail | a full or collapsed reference link whose label has no definition |
|
|
975
983
|
|
|
984
|
+
### sequence
|
|
985
|
+
|
|
986
|
+
Runs when the spec declares `form.sequence`, and holds a work read in order to what its reader
|
|
987
|
+
depends on: every lesson carries its sections, every term is defined once, no lesson uses a term
|
|
988
|
+
before the lesson that defines it, and each lesson defines what the outline promises. How a unit is
|
|
989
|
+
read, and each guard, are under [Sequential works](#sequential-works). With no `form.sequence` the
|
|
990
|
+
station skips. It reads words, never meaning: a term used in another sense still counts as a use.
|
|
991
|
+
|
|
992
|
+
| Id | Severity | Meaning |
|
|
993
|
+
|---|---|---|
|
|
994
|
+
| `station-sequence-no-units` | fail | the draft has no `<unit> <n>` heading |
|
|
995
|
+
| `station-sequence-numbering` | fail | a unit's number is not greater than the one before it |
|
|
996
|
+
| `station-sequence-missing-section` | fail | a unit lacks one of `sections`; names the unit and the section |
|
|
997
|
+
| `station-sequence-defined-twice` | fail | a term is defined in two units' terms sections; names both |
|
|
998
|
+
| `station-sequence-used-before-defined` | fail | a unit uses a term before the unit that defines it; points at the first use |
|
|
999
|
+
| `station-sequence-outline-unreadable` | fail | `outline` is set and the file cannot be read |
|
|
1000
|
+
| `station-sequence-outline-unkept` | fail | the outline promises a term in a unit that does not define it |
|
|
1001
|
+
| `station-sequence-forward-pointer` | warn | a unit mentions a later unit by number, once per pair |
|
|
1002
|
+
|
|
976
1003
|
### Any station
|
|
977
1004
|
|
|
978
1005
|
| Id | Severity | Meaning |
|
|
@@ -985,11 +1012,13 @@ Each `check` appends one line to the spec's `improvement.ledger`, the same file
|
|
|
985
1012
|
reads:
|
|
986
1013
|
|
|
987
1014
|
```json
|
|
988
|
-
{"at":"2026-09-29T13:21:37.330Z","kind":"check","draft":"essay/draft.md","draft_sha256":"<sha256 of the draft>","spec_sha256":"<sha256 of the spec>","stations":{"form":"pass","terms":"pass","claims":"pass","quotes":"pass","private":"pass","dna":"pass","links":"pass"},"verdict":"one-shot"}
|
|
1015
|
+
{"at":"2026-09-29T13:21:37.330Z","kind":"check","draft":"essay/draft.md","draft_sha256":"<sha256 of the draft>","spec_sha256":"<sha256 of the spec>","stations":{"form":"pass","terms":"pass","claims":"pass","quotes":"pass","private":"pass","dna":"pass","links":"pass","sequence":"skip"},"verdict":"one-shot"}
|
|
989
1016
|
```
|
|
990
1017
|
|
|
991
1018
|
`draft` is the draft's path relative to the spec's folder, however you spelled it, so one draft
|
|
992
|
-
has one history.
|
|
1019
|
+
has one history. A work checked from `form.sequence.files` is keyed by that list as the spec writes
|
|
1020
|
+
it, joined with ", ", so the work keeps one history as parts are added, and its `draft_sha256`
|
|
1021
|
+
covers every file's name and bytes. `draft_sha256` and `spec_sha256` hash the two files' bytes; the files the spec
|
|
993
1022
|
names (materials, the claims ledger, a scope folder) are not hashed, so "changed" below means the
|
|
994
1023
|
draft or the spec. `stations` holds each station's status.
|
|
995
1024
|
|
|
@@ -1010,6 +1039,100 @@ the same draft:
|
|
|
1010
1039
|
|
|
1011
1040
|
A ledger path that leads outside the spec's folder is not written, and `check` prints a warning.
|
|
1012
1041
|
|
|
1042
|
+
## Sequential works
|
|
1043
|
+
|
|
1044
|
+
A course, a primer, a textbook, a book of lessons: a work read in order makes a promise no single
|
|
1045
|
+
piece can check. Lesson 5 is written for someone who has read Lessons 1 to 4 and nothing else, so
|
|
1046
|
+
every word it uses was defined there. That promise lives across pieces, and it breaks one edit at a
|
|
1047
|
+
time: a term moves, a lesson is reordered, a sentence borrows a word from a lesson the reader has
|
|
1048
|
+
not reached. Declare the work a sequence and the `sequence` station checks the promise on every run.
|
|
1049
|
+
|
|
1050
|
+
### Declaring a sequence
|
|
1051
|
+
|
|
1052
|
+
Add `sequence:` to `writing.form`. Every key is optional; this is the course example's, with the
|
|
1053
|
+
four that have defaults written out:
|
|
1054
|
+
|
|
1055
|
+
```yaml
|
|
1056
|
+
sequence:
|
|
1057
|
+
unit: Lesson
|
|
1058
|
+
files:
|
|
1059
|
+
- course/part-*.md
|
|
1060
|
+
sections:
|
|
1061
|
+
- After this lesson you can
|
|
1062
|
+
- New terms
|
|
1063
|
+
- Try this
|
|
1064
|
+
terms_section: New terms
|
|
1065
|
+
outline: course/outline.md
|
|
1066
|
+
teaser: Next,
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
| Key | Default | What it is |
|
|
1070
|
+
|---|---|---|
|
|
1071
|
+
| `unit` | `Lesson` | the word each unit's heading starts with: `## Lesson 3: Title` |
|
|
1072
|
+
| `files` | none | the work's files in reading order, relative to the spec. A `*` in a file name matches within its folder, sorted by number, so `part-2.md` comes before `part-10.md` and a new part is picked up without editing the spec |
|
|
1073
|
+
| `sections` | the three shown | what every unit carries, each as a `**Label:**` line or a heading |
|
|
1074
|
+
| `terms_section` | `New terms` | the section whose list defines the unit's terms |
|
|
1075
|
+
| `outline` | none | an outline whose numbered items promise each unit's terms as `*Terms: a, b.*` |
|
|
1076
|
+
| `knows` | none | words a unit may use before a unit defines them, beside `audience.knows` |
|
|
1077
|
+
| `teaser` | `Next,` | a closing line starting `**<teaser>` names what is coming; it and everything after it in the unit are exempt from the order guard and the pointer warning |
|
|
1078
|
+
|
|
1079
|
+
### How a unit is read
|
|
1080
|
+
|
|
1081
|
+
A unit starts at an ATX heading that begins with `unit` and a number, and runs to the next unit
|
|
1082
|
+
heading, the next heading of its own level or above, or the end of its file. So a part's own
|
|
1083
|
+
heading and introduction belong to no lesson, and neither does a file's YAML frontmatter, which is
|
|
1084
|
+
blanked before anything reads the file. A heading inside fenced code is not a heading.
|
|
1085
|
+
|
|
1086
|
+
The terms section is its label line and the list under it, to the first blank line after the list.
|
|
1087
|
+
Each item defines every bold term before its first `:**`, so `- **Claude Code**, **Codex** and
|
|
1088
|
+
**Claude Cowork:** ...` defines three. A term is compared in lower case, with code and emphasis
|
|
1089
|
+
marks and any parenthetical dropped. An item that says `(from Lesson 3)` reminds the reader of an
|
|
1090
|
+
earlier term and defines nothing.
|
|
1091
|
+
|
|
1092
|
+
A use is a whole-word match, ignoring case, where a hyphen joins a word: "context-aware" does not
|
|
1093
|
+
use "context", and "skills" does not use "skill". Code is not prose: fenced blocks and inline code
|
|
1094
|
+
never count as a use, so `@supersuit/superskill` does not use "supersuit". A unit's own terms
|
|
1095
|
+
section is not a use either. Listing a word in `knows` or `audience.knows` is a decision that the
|
|
1096
|
+
reader already has it, and it is the only way a unit may use a word before the unit that defines it.
|
|
1097
|
+
|
|
1098
|
+
### Checking a sequence
|
|
1099
|
+
|
|
1100
|
+
With `files` listed, `check` needs no `--draft`: the files, joined in reading order, are the draft,
|
|
1101
|
+
and every station reads them together. A finding names the file and its own line.
|
|
1102
|
+
|
|
1103
|
+
```bash
|
|
1104
|
+
npx @supersuit/hyperspec check course.hyperspec.md
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
```
|
|
1108
|
+
form: pass
|
|
1109
|
+
terms: skip (writing.audience.terms is empty or not set)
|
|
1110
|
+
claims: pass
|
|
1111
|
+
quotes: pass
|
|
1112
|
+
private: pass
|
|
1113
|
+
dna: skip (writing.dna.scope_dir is not set)
|
|
1114
|
+
links: pass
|
|
1115
|
+
sequence: pass
|
|
1116
|
+
warn [station-sequence-forward-pointer] Lesson 1 points forward to Lesson 4 (course/part-1.md line 25)
|
|
1117
|
+
fix: Keep it a pointer ("more in Lesson 4"): Lesson 1 must make sense to a reader who has not read Lesson 4.
|
|
1118
|
+
warn [station-sequence-forward-pointer] Lesson 3 points forward to Lesson 4 (course/part-2.md line 24)
|
|
1119
|
+
fix: Keep it a pointer ("more in Lesson 4"): Lesson 3 must make sense to a reader who has not read Lesson 4.
|
|
1120
|
+
verdict: one-shot
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
`--draft` still works on a sequence spec: it names one file, which may hold every lesson under
|
|
1124
|
+
repeated headings. Checked from `files`, `form.length` and `required_parts` apply to the whole work,
|
|
1125
|
+
so write `required_parts` as the part headings. A relative link resolves beside the file that holds
|
|
1126
|
+
it.
|
|
1127
|
+
|
|
1128
|
+
The outline guard checks only the units the draft holds, so a work is checked while it is being
|
|
1129
|
+
written: an outline that promises Lessons 1 to 28 checks a draft of Lessons 1 to 20 without
|
|
1130
|
+
complaint. A pointer to a later unit is a warning, never a failure: "more in Lesson 12" is fine as
|
|
1131
|
+
long as the lesson makes sense without it.
|
|
1132
|
+
|
|
1133
|
+
It cannot tell whether a definition is good, whether a term is used in the sense it was defined in,
|
|
1134
|
+
or whether a quiz tests what the lessons taught. The judges still take one `--draft` file.
|
|
1135
|
+
|
|
1013
1136
|
## Judging a draft
|
|
1014
1137
|
|
|
1015
1138
|
`check` runs the stations that are plain functions of the spec and the draft. The rest of a
|
|
@@ -1583,7 +1706,7 @@ not exist.
|
|
|
1583
1706
|
|
|
1584
1707
|
## Worked examples
|
|
1585
1708
|
|
|
1586
|
-
|
|
1709
|
+
Three complete specs ship in [`examples/writing/`](examples/writing/), each with every file it
|
|
1587
1710
|
names:
|
|
1588
1711
|
|
|
1589
1712
|
- `essay.hyperspec.md`: an essay for new managers on running a first one-on-one. Three materials
|
|
@@ -1592,8 +1715,11 @@ names:
|
|
|
1592
1715
|
- `story.hyperspec.md`: a short story, `fiction: true`, narrated by one of its two characters.
|
|
1593
1716
|
Each character has speech rules, a knowledge timeline by scene, and golden and rejected lines
|
|
1594
1717
|
in a voice you can tell apart from the other's.
|
|
1718
|
+
- `course.hyperspec.md`: a four-lesson course in two part files, declared a sequence (see
|
|
1719
|
+
[Sequential works](#sequential-works)), with an outline promising each lesson's terms. `check`
|
|
1720
|
+
reads the parts with no `--draft` and passes them, with two forward-pointer warnings.
|
|
1595
1721
|
|
|
1596
|
-
Every material in
|
|
1722
|
+
Every material in all three is marked. Between them the essay and the story use all seven labels, each with
|
|
1597
1723
|
the field it needs, and every spine claim cites the segments that support it. Each segments file
|
|
1598
1724
|
keeps the boundaries `segments init` wrote, in paragraph mode for prose and sentence mode for
|
|
1599
1725
|
bulleted notes, so you can re-run it and compare.
|
|
@@ -1602,8 +1728,8 @@ Both lint `pass (9/9)` with `writing: 9/9 blocks complete` and no findings. Each
|
|
|
1602
1728
|
draft written to it, `essay/draft.md` and `story/draft.md`, with its claims ledger beside it, and
|
|
1603
1729
|
both drafts pass every station of `check`: the essay with one dna warning, described under
|
|
1604
1730
|
[dna](#dna), and the story with dna skipped, since it names no scope folder, and quotes skipped,
|
|
1605
|
-
since it is fiction.
|
|
1606
|
-
specs and checks
|
|
1731
|
+
since it is fiction. The course lints the same way and its parts pass every station. A test
|
|
1732
|
+
lints all three specs and checks their drafts on every release, so they cannot drift from the tool.
|
|
1607
1733
|
|
|
1608
1734
|
Each also ships the packets `judge prepare` writes for its draft, in `essay/judge/` and
|
|
1609
1735
|
`story/judge/`, and one sample verdict per packet in `essay/sample-verdicts/` and
|
|
@@ -1615,8 +1741,9 @@ is what `prepare` writes now and records every sample.
|
|
|
1615
1741
|
|
|
1616
1742
|
## What later versions add
|
|
1617
1743
|
|
|
1618
|
-
This release is the schema, its lint, marked materials, scoped DNA, `check` with
|
|
1619
|
-
deterministic stations, six judgment stations written as packets for
|
|
1744
|
+
This release is the schema, its lint, marked materials, scoped DNA, `check` with eight
|
|
1745
|
+
deterministic stations (sequential works among them), six judgment stations written as packets for
|
|
1746
|
+
an outside judge, and learn.
|
|
1620
1747
|
Next: lineups over several passages of one draft, so that one lucky pick carries less weight, and
|
|
1621
1748
|
a learn step that reads the runs ledger across drafts for the stations that keep failing and the
|
|
1622
1749
|
changes that made them pass, beside what one pair of drafts shows.
|
package/bin/hyperspec.mjs
CHANGED
|
@@ -24,8 +24,10 @@ const HELP = `hyperspec <command> [options]
|
|
|
24
24
|
|
|
25
25
|
lint <file...> [--json] score each hyperspec against the nine tests
|
|
26
26
|
exit 0 pass, 1 a test fails, 3 blocked on an open decision, 2 usage
|
|
27
|
-
check <spec> --draft <file> [--json] [--only a,b]
|
|
28
|
-
needs a writing spec (profile: writing)
|
|
27
|
+
check <spec> [--draft <file>] [--json] [--only a,b]
|
|
28
|
+
needs a writing spec (profile: writing), and --draft unless the
|
|
29
|
+
spec lists writing.form.sequence.files, whose files, joined in
|
|
30
|
+
reading order, are then the draft; lints it first (a spec
|
|
29
31
|
that does not pass lint, or is blocked, exits with lint's own code
|
|
30
32
|
and runs no station: a draft cannot be checked against a spec that
|
|
31
33
|
is not ready); then runs every deterministic station (or the
|
|
@@ -358,7 +360,6 @@ if (cmd === "check") {
|
|
|
358
360
|
process.exit(2);
|
|
359
361
|
};
|
|
360
362
|
if (!specPath) usage("check needs a spec path");
|
|
361
|
-
if (!parsed.values["--draft"]) usage("check needs --draft <file>");
|
|
362
363
|
let only;
|
|
363
364
|
if (parsed.values["--only"] !== undefined) {
|
|
364
365
|
only = parsed.values["--only"].split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -389,7 +390,7 @@ if (cmd === "check") {
|
|
|
389
390
|
for (const s of result.stations) {
|
|
390
391
|
if (s.status === "skip") { console.log(`${s.station}: skip (${s.reason})`); continue; }
|
|
391
392
|
console.log(`${s.station}: ${s.status}`);
|
|
392
|
-
for (const finding of s.findings) console.log(` ${finding.severity === "fail" ? "fail" : "warn"} [${finding.id}] ${finding.message}${typeof finding.line === "number" ? ` (line ${finding.line})` : ""}\n fix: ${finding.fix}`);
|
|
393
|
+
for (const finding of s.findings) console.log(` ${finding.severity === "fail" ? "fail" : "warn"} [${finding.id}] ${finding.message}${typeof finding.line === "number" ? ` (${finding.file ? `${finding.file} ` : ""}line ${finding.line})` : ""}\n fix: ${finding.fix}`);
|
|
393
394
|
}
|
|
394
395
|
if (result.verdict) {
|
|
395
396
|
const detail = result.verdictDetail.change ?? result.verdictDetail.reason;
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Mix the flour and the water with your hand until no dry flour is left. It will look wrong. It is supposed to look wrong. Leave it for half an hour and come back.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Course brief, written by the author after teaching the class twice.
|
|
2
|
+
|
|
3
|
+
Nobody who has never baked needs a recipe first. They need four words they do not have yet: dough, starter, proof and crumb. Every class that went badly went badly because I used one of those words before I had said what it meant.
|
|
4
|
+
|
|
5
|
+
The order matters more than the recipes. Water and flour first, then what makes it rise, then shaping, then the oven. Each lesson should stand only on the ones before it.
|
|
6
|
+
|
|
7
|
+
Each lesson ends with one thing to do in a real kitchen, because nobody learns bread by reading about it.
|
|
8
|
+
|
|
9
|
+
I once taught shaping before proofing and half the room shaped dough that had not risen. I never did it again.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{"material":"brief","path":"course/materials/brief.md","sha256":"7fc69cebae3218131d8e920af1408b42505ebfe2ccb4f72e9eb9c9ac9fb48b24"}
|
|
2
|
+
{"id":"s1","start":0,"end":67,"label":"aside","text":"Course brief, written by the author after teaching the class twice."}
|
|
3
|
+
{"id":"s2","start":69,"end":299,"label":"claim","own":true,"text":"Nobody who has never baked needs a recipe first. They need four words they do not have yet: dough, starter, proof and crumb. Every class that went badly went badly because I used one of those words before I had said what it meant."}
|
|
4
|
+
{"id":"s3","start":301,"end":471,"label":"claim","own":true,"text":"The order matters more than the recipes. Water and flour first, then what makes it rise, then shaping, then the oven. Each lesson should stand only on the ones before it."}
|
|
5
|
+
{"id":"s4","start":473,"end":578,"label":"stance","text":"Each lesson ends with one thing to do in a real kitchen, because nobody learns bread by reading about it."}
|
|
6
|
+
{"id":"s5","start":580,"end":690,"label":"story","teller":"example-author","text":"I once taught shaping before proofing and half the room shaped dough that had not risen. I never did it again."}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Bread from zero: the outline
|
|
2
|
+
|
|
3
|
+
## Part 1: Dough
|
|
4
|
+
|
|
5
|
+
1. **Flour and water.** What happens when the two meet. *Terms: dough, hydration.*
|
|
6
|
+
2. **What makes it rise.** A living culture you keep in a jar. *Terms: starter, proof.*
|
|
7
|
+
|
|
8
|
+
## Part 2: The bake
|
|
9
|
+
|
|
10
|
+
3. **Shaping.** Turning a slack mass into a loaf that holds itself up. *Terms: bench rest, surface tension.*
|
|
11
|
+
4. **The oven.** What heat does in the first ten minutes, and how to read the inside. *Terms: oven spring, crumb.*
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Bread from zero, Part 1: Dough"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Bread from zero
|
|
6
|
+
|
|
7
|
+
## Part 1: Dough
|
|
8
|
+
|
|
9
|
+
This course assumes you have never baked a loaf. Every word it needs is defined in the lesson that first uses it.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Lesson 1: Flour and water
|
|
14
|
+
|
|
15
|
+
**After this lesson you can:** mix a dough and say how wet it is.
|
|
16
|
+
|
|
17
|
+
**New terms:**
|
|
18
|
+
- **Dough:** flour and water mixed until no dry flour is left.
|
|
19
|
+
- **Hydration:** how much water a dough holds for its flour, as a share of the flour's weight. Five hundred grams of flour and three hundred and fifty of water is seventy percent.
|
|
20
|
+
|
|
21
|
+
Put five hundred grams of flour in a bowl and pour in three hundred and fifty grams of water. Mix with your hand until nothing dry is left. That is a dough.
|
|
22
|
+
|
|
23
|
+
It will look wrong: shaggy, sticky, nothing like bread. Leave it covered for half an hour. The water is still working its way into the flour, and when you come back the dough will be smoother without your having done anything.
|
|
24
|
+
|
|
25
|
+
The higher the hydration, the stickier the dough and the more open the bread. Seventy percent is a forgiving place to start. Lesson 4 shows you what hydration does inside a finished loaf.
|
|
26
|
+
|
|
27
|
+
**Try this:** mix one dough at sixty percent and one at seventy-five, and press a finger into each after half an hour.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Lesson 2: What makes it rise
|
|
32
|
+
|
|
33
|
+
**After this lesson you can:** keep a starter alive and tell when a dough has risen enough.
|
|
34
|
+
|
|
35
|
+
**New terms:**
|
|
36
|
+
- **Starter:** flour and water left to ferment, kept in a jar and fed every day. It is what makes the dough rise.
|
|
37
|
+
- **Proof:** the rise itself, the hours a dough spends growing before it is shaped and baked.
|
|
38
|
+
|
|
39
|
+
Mix fifty grams of flour and fifty of water in a jar, loosely covered. Feed it the same again every day. In about a week it will double within hours of a feed and smell sour. It is alive, and it is ready.
|
|
40
|
+
|
|
41
|
+
Add a spoon of starter to the dough from Lesson 1 and leave it somewhere warm. The dough is now proofing. It is done when it has grown by half and a finger pressed into it leaves a dent that fills back slowly.
|
|
42
|
+
|
|
43
|
+
**Try this:** start a starter today, and mark the jar with tape at its height after each feed.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
**Next, Part 2: The bake.** Shaping a loaf that holds itself up, and what happens to the crumb in the oven.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Bread from zero, Part 2: The bake"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Bread from zero
|
|
6
|
+
|
|
7
|
+
## Part 2: The bake
|
|
8
|
+
|
|
9
|
+
Part 1 made a dough and made it rise. This part turns it into bread.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Lesson 3: Shaping
|
|
14
|
+
|
|
15
|
+
**After this lesson you can:** shape a proofed dough into a loaf that holds itself up.
|
|
16
|
+
|
|
17
|
+
**New terms:**
|
|
18
|
+
- **Bench rest:** twenty minutes a dough sits on the counter between a rough shape and the final one, so it relaxes enough to be shaped again.
|
|
19
|
+
- **Surface tension:** the tight skin you pull across the top of a loaf, which is what lets it stand instead of spreading.
|
|
20
|
+
- **Dough** (from Lesson 1): flour and water mixed until no dry flour is left.
|
|
21
|
+
|
|
22
|
+
Tip the proofed dough onto the counter and fold it into a rough ball. Give it a bench rest. Then flip it, fold the far edge to the middle, and roll it toward you, pulling the top tight as you go. That tightness is surface tension, and a loaf without it spreads in the oven.
|
|
23
|
+
|
|
24
|
+
**Try this:** shape two loaves, one pulled tight and one left loose, and bake them side by side after Lesson 4.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Lesson 4: The oven
|
|
29
|
+
|
|
30
|
+
**After this lesson you can:** bake a loaf and read what its inside tells you.
|
|
31
|
+
|
|
32
|
+
**New terms:**
|
|
33
|
+
- **Oven spring:** the last rise a loaf takes in the first ten minutes of heat, before the crust sets.
|
|
34
|
+
- **Crumb:** the inside of a baked loaf: the holes, their size, and how they are spread.
|
|
35
|
+
|
|
36
|
+
Heat the oven as hot as it goes, with a heavy pot inside. Put the loaf in the pot, cover it, and bake for twenty minutes, then uncover it and bake until it is dark. The covered minutes are the oven spring: steam keeps the crust soft while the loaf grows.
|
|
37
|
+
|
|
38
|
+
Let it cool for an hour before you cut it. Then read the crumb. Big uneven holes mean a well proofed, high hydration dough. A tight, even crumb with a dense band at the bottom means the proof was too short.
|
|
39
|
+
|
|
40
|
+
**Try this:** cut the two loaves from Lesson 3 and compare their crumb.
|
|
File without changes
|