@supersuit/hyperspec 0.2.0 → 0.4.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 +119 -0
- package/README.md +53 -3
- package/SPEC.md +24 -10
- package/WRITING.md +597 -0
- package/bin/hyperspec.mjs +94 -3
- package/examples/minimal.hyperspec.md +2 -2
- package/examples/writing/essay/goldens/close.md +2 -0
- package/examples/writing/essay/goldens/opening.md +2 -0
- package/examples/writing/essay/materials/interview-notes.md +12 -0
- package/examples/writing/essay/materials/interview-notes.md.segments.jsonl +10 -0
- package/examples/writing/essay/materials/team-survey.md +7 -0
- package/examples/writing/essay/materials/team-survey.md.segments.jsonl +6 -0
- package/examples/writing/essay/materials/voice-memo.md +18 -0
- package/examples/writing/essay/materials/voice-memo.md.segments.jsonl +7 -0
- package/examples/writing/essay/runs.jsonl +0 -0
- package/examples/writing/essay.hyperspec.md +220 -0
- package/examples/writing/story/goldens/dialogue.md +3 -0
- package/examples/writing/story/goldens/opening.md +3 -0
- package/examples/writing/story/materials/bakery-visit.md +9 -0
- package/examples/writing/story/materials/bakery-visit.md.segments.jsonl +13 -0
- package/examples/writing/story/materials/notes.md +16 -0
- package/examples/writing/story/materials/notes.md.segments.jsonl +7 -0
- package/examples/writing/story/materials/scene-list.md +7 -0
- package/examples/writing/story/materials/scene-list.md.segments.jsonl +14 -0
- package/examples/writing/story/runs.jsonl +0 -0
- package/examples/writing/story.hyperspec.md +288 -0
- package/examples/writing/style-rules.md +19 -0
- package/package.json +4 -2
- package/src/blobs.mjs +1 -1
- package/src/compare.mjs +6 -6
- package/src/fsutil.mjs +1 -1
- package/src/labels.mjs +6 -0
- package/src/placeholder.mjs +20 -0
- package/src/profiles.mjs +50 -0
- package/src/reproduce.mjs +5 -5
- package/src/rules.mjs +25 -13
- package/src/score.mjs +7 -1
- package/src/segments.mjs +407 -0
- package/src/template.mjs +4 -1
- package/src/writing-exports.mjs +6 -0
- package/src/writing-fields.mjs +418 -0
- package/src/writing-template.mjs +199 -0
- package/src/writing.mjs +181 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,124 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 (2026-09-29)
|
|
4
|
+
|
|
5
|
+
Marking materials. Before a writing spec can pass, every material it draws on (a brain dump, a
|
|
6
|
+
transcript, a set of interview notes) is split into segments, and each segment is labeled with
|
|
7
|
+
what a draft may use it as: a claim with its source, the author's own claim, a story with its
|
|
8
|
+
teller, a quote with its speaker, a stance, an open question, an aside, or something private.
|
|
9
|
+
The spine then cites segments rather than whole files, so every claim points at the exact words
|
|
10
|
+
behind it. hyperspec splits and checks; an agent or a person labels. It still calls no model.
|
|
11
|
+
|
|
12
|
+
**Behavior change for 0.3 writing specs:** a writing spec's materials must now be marked. A
|
|
13
|
+
material item with no `segments:` field fails test 1, so a writing spec that passed 0.3.0 fails
|
|
14
|
+
until each of its materials has a segments file, written with `hyperspec segments init` and
|
|
15
|
+
labeled. Specs with no profile are unaffected.
|
|
16
|
+
|
|
17
|
+
- `hyperspec segments init <material> --id <mid> [--out <file>] [--by paragraph|sentence]`
|
|
18
|
+
writes `<material>.segments.jsonl`: a header naming the material, its path and the SHA-256 of
|
|
19
|
+
its bytes, then one line per segment with character offsets, the verbatim text, and the label
|
|
20
|
+
`unlabeled`. Paragraph mode is the default. It refuses to overwrite a file, and exits 2 with a
|
|
21
|
+
plain message on a missing material, a material with nothing in it, an `--out` folder that
|
|
22
|
+
does not exist, or an unknown `--by`.
|
|
23
|
+
- In sentence mode, a new line that opens on a list marker (`-`, `*`, `+`, or a number followed
|
|
24
|
+
by `.` or `)`, then a space) starts a new segment, and a numbered item's own `1.` is not read
|
|
25
|
+
as a sentence ending. A bullet that is entirely a quotation ending in `."` used to run into the
|
|
26
|
+
next bullet; it now stands on its own. Paragraph mode is unchanged.
|
|
27
|
+
- Seven labels, a closed set: `claim` (needs `source`, or `own: true`), `story` (needs
|
|
28
|
+
`teller`), `quote` (needs `speaker`), `stance`, `question`, `aside` and `private`. `unlabeled`
|
|
29
|
+
is never accepted. A placeholder word (`TODO`, `n/a`, `tbd`, `...`, `???` and the rest) counts
|
|
30
|
+
as missing in these fields, in the header, and in segment ids, as it does everywhere else in
|
|
31
|
+
the linter.
|
|
32
|
+
- What `lint` checks on each segments file, under test 1: the file exists and parses, its header
|
|
33
|
+
names the right material, every label is from the set, ids are unique, and segments never
|
|
34
|
+
overlap and cover every character that is not whitespace (the finding quotes the first
|
|
35
|
+
uncovered text and gives its offset), and the material has some text to mark. Under test 4: every segment's text
|
|
36
|
+
matches the material word for word, each label carries the field it needs, and the material
|
|
37
|
+
has not changed since it was marked (its SHA-256 still matches). A changed material fails as
|
|
38
|
+
stale until it is marked again.
|
|
39
|
+
- A spine claim may cite `material#segment`. The segment must exist (test 4), and citing a
|
|
40
|
+
`private` or `question` segment fails test 5. A bare material id still cites the whole
|
|
41
|
+
material; `material#` with nothing after the `#` fails as an unknown segment.
|
|
42
|
+
- Every marking finding id starts `writing-materials-` or `writing-spine-`, and every message
|
|
43
|
+
names the material, and the segment where there is one. Paths in messages read as the spec
|
|
44
|
+
wrote them, never resolved to a folder on your machine, so `--json` output is the same
|
|
45
|
+
everywhere. WRITING.md lists every finding with its test.
|
|
46
|
+
- A new export, `@supersuit/hyperspec/writing`, gives your own tools `MATERIAL_LABELS` and
|
|
47
|
+
`readSegments`, the same parse-and-check lint runs. `readSegments` returns
|
|
48
|
+
`{ header, segments, findings }` and never throws; `displayPath` and `materialDisplayPath` set
|
|
49
|
+
how the files are named in its messages.
|
|
50
|
+
- `hyperspec init --profile writing` names a segments file for its placeholder material, and the
|
|
51
|
+
materials check reads "every segment of every material carries a label from the closed set,
|
|
52
|
+
matches its source verbatim, and the markings are current".
|
|
53
|
+
- Both writing examples ship with every material marked. Between them they use all seven labels,
|
|
54
|
+
and every spine claim cites segments.
|
|
55
|
+
- WRITING.md gains a Marking materials section: the file format with a worked sample, the labels
|
|
56
|
+
and what each needs, coverage, staleness (including a line-ending conversion, which changes the
|
|
57
|
+
hash), citing segments, every finding with its test, and the import. README and SPEC.md point
|
|
58
|
+
at it.
|
|
59
|
+
- The repository's `.gitattributes` keeps example materials and test fixtures LF on every
|
|
60
|
+
checkout, so the hashes their segments files pin still match on a Windows clone.
|
|
61
|
+
|
|
62
|
+
## 0.3.0 (2026-09-29)
|
|
63
|
+
|
|
64
|
+
The writing profile. A piece of writing can now carry a hyperspec that names everything an
|
|
65
|
+
agent would otherwise fill with the average: what the piece is made from and how far each
|
|
66
|
+
source can be trusted, whose voice it is for this form, audience and purpose, who it speaks as,
|
|
67
|
+
who reads it, the one change it is for, what kind of thing it is, the claims it argues in order,
|
|
68
|
+
where every fact comes from, and in fiction how each character speaks and what they know by
|
|
69
|
+
each scene. `hyperspec lint` checks all of it under the same nine tests, and
|
|
70
|
+
`hyperspec init --profile writing` lays every block out for you to fill in.
|
|
71
|
+
|
|
72
|
+
**Behavior change for existing specs:** a placeholder now counts as missing, everywhere, in
|
|
73
|
+
specs with no profile too. A placeholder is a whole value, trimmed and in any case, of `todo`,
|
|
74
|
+
`tbd`, `fixme`, `xxx`, `placeholder`, `<placeholder>`, `n/a`, a run of dashes, a run of question
|
|
75
|
+
marks, or an ellipsis, optionally followed by a trailing `.`, `:` or `!`. A spec that passed
|
|
76
|
+
0.2.0 with `source: TODO` or `source: n/a` on a decision now fails that test. Real text that
|
|
77
|
+
starts with one of those, such as `TODO: write the opening`, still counts as present, and so
|
|
78
|
+
does `none`.
|
|
79
|
+
|
|
80
|
+
- `profile: writing` opts a spec in; its blocks live under a top-level `writing:` map:
|
|
81
|
+
`materials`, `dna`, `persona`, `audience`, `goal`, `form`, `spine`, `sources`, and
|
|
82
|
+
`characters`, which is required when `fiction: true`. Every block carries a `check`, a
|
|
83
|
+
`source` and an `author`. The core format still applies in full.
|
|
84
|
+
- No tenth test. Every writing finding reports under one of the nine, with an id starting
|
|
85
|
+
`writing-`, and the score stays out of nine. `lint` prints one more line,
|
|
86
|
+
`writing: <k>/9 blocks complete`, and `--json` carries it as `profile` on each file.
|
|
87
|
+
- A missing block fails test 1 unless a decision with the id `writing-<block>` defers it:
|
|
88
|
+
`open`, which blocks the spec like any open decision, or `delegated` with a `rule`. A
|
|
89
|
+
deferred block does not count as complete.
|
|
90
|
+
- Progress is never stored: a `writing.progress` key fails test 7, because saved progress goes
|
|
91
|
+
stale the first time a session dies mid-arc. Progress is read off the folder instead.
|
|
92
|
+
- Field rules for every block, each under the test it belongs to: closed sets for `fiction`
|
|
93
|
+
(absent means false), `trust`, `reader`, `change.kind`, the shape of `persona.identity` and
|
|
94
|
+
`unsourced_claim`; unique ids for materials, spine claims and characters; a length envelope
|
|
95
|
+
of whole numbers of at least 1 (test 1); five to ten distinct `goal.conditions` naming real
|
|
96
|
+
requirements, each listed once (test 2); spine claims that name real materials (test 4);
|
|
97
|
+
`persona.facts_from: sources` and a non-empty `will_not_say` (test 5); a `why` on every
|
|
98
|
+
golden, material and golden paths that exist, and golden and rejected lines for every
|
|
99
|
+
character with no line in both (test 6). A repeated id counts once toward a minimum. A
|
|
100
|
+
`stance` outside peer, mentor, witness and guide is a warning.
|
|
101
|
+
- A character needs speech rules (what they say and never say), wants, fears, what they hide,
|
|
102
|
+
an arc state, a knowledge timeline, and golden and rejected lines. `relationships` and a
|
|
103
|
+
pointer to a character `entity` file are optional.
|
|
104
|
+
- `hyperspec init <file> --profile writing [--title T] [--form F] [--fiction]` writes a
|
|
105
|
+
skeleton with every required block in schema order, every field a placeholder, and open
|
|
106
|
+
decisions for the four blocks that need your judgment first (dna, persona, audience, goal).
|
|
107
|
+
It fails lint until the placeholders are replaced. `init` exits 2 with a plain message on a
|
|
108
|
+
`--profile` with no value or one it does not know, `--fiction` or `--form` without
|
|
109
|
+
`--profile writing`, `--kind` with it, and a folder that does not exist.
|
|
110
|
+
- `lint` warns under test 7 on a `profile` it does not know, and checks none of its rules. A
|
|
111
|
+
name every object inherits, such as `constructor`, is an unknown profile like any other.
|
|
112
|
+
- WRITING.md documents the profile: the ten components, the schema with every field, the test
|
|
113
|
+
mapping, the closed sets, the seven materials labels and what each may be used as, and
|
|
114
|
+
deferral. It ships in the package.
|
|
115
|
+
- `examples/writing/` ships two complete specs, an essay and a two-character short story, with
|
|
116
|
+
every file they name. Both pass with no findings, and a test keeps them that way.
|
|
117
|
+
- SPEC.md gains a Profiles section and states the placeholder words in its test-to-field map.
|
|
118
|
+
- The illustrative specs and recipe in SPEC.md and `examples/minimal.hyperspec.md` name a
|
|
119
|
+
placeholder author, `example-author`, and a placeholder factory, `my-factory`.
|
|
120
|
+
- Requires `@supersuit/superskill` 0.2.2, whose YAML reader reads an inline map (`scope: { form: essay, purpose: persuade }`), a bare inline map as a list item, and an inline list followed by a comment. On 0.2.1 those came back as text, so a correct spec written in that compact style failed. A test lints the essay example rewritten in that style.
|
|
121
|
+
|
|
3
122
|
## 0.2.0 (2026-09-28)
|
|
4
123
|
|
|
5
124
|
Recipes. Every output a factory makes can now carry a recipe beside it: what made it, from
|
package/README.md
CHANGED
|
@@ -24,13 +24,15 @@ improvement ledger. Every test is defined in [SPEC.md](SPEC.md).
|
|
|
24
24
|
|---|---|
|
|
25
25
|
| `hyperspec lint <file...> [--json]` | Score each hyperspec against the nine tests. |
|
|
26
26
|
| `hyperspec init <file> [--title T] [--kind K]` | Write a new hyperspec skeleton. Refuses to overwrite an existing file. |
|
|
27
|
+
| `hyperspec init <file> --profile writing [--title T] [--form F] [--fiction]` | Write a writing-spec skeleton, every block shown with placeholders. |
|
|
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. |
|
|
27
29
|
| `hyperspec recipe check <output-or-recipe>` | Check that a recipe records everything the standard asks for. |
|
|
28
30
|
| `hyperspec recipe approve <recipe> --by <slug>` | Record who approved the output. |
|
|
29
31
|
| `hyperspec reproduce <recipe> [--restore]` | Re-check every hash the recipe recorded. Never runs a model. |
|
|
30
32
|
| `hyperspec regenerate <recipe> --out <path> --clicker <slug> <one change> [--run cmd]` | Make a child recipe from a parent and one named change, rerunning only the stages it reaches. |
|
|
31
33
|
| `hyperspec compare <child-recipe> --doctor cmd` | Grade a child and its parent through one doctor against one spec. |
|
|
32
34
|
|
|
33
|
-
Every command except `init` takes `--json`. `hyperspec --help` prints every flag.
|
|
35
|
+
Every command except `init` and `segments init` takes `--json`. `hyperspec --help` prints every flag.
|
|
34
36
|
|
|
35
37
|
## Exit codes
|
|
36
38
|
|
|
@@ -103,11 +105,59 @@ recipe.finish();
|
|
|
103
105
|
The schema, the stage key, the runner and doctor contracts, and every exit code are in
|
|
104
106
|
[SPEC.md](SPEC.md#recipes).
|
|
105
107
|
|
|
108
|
+
## Writing specs
|
|
109
|
+
|
|
110
|
+
A piece of writing gets its own profile. Add `profile: writing` to a hyperspec and it gains nine
|
|
111
|
+
blocks that name what an agent would otherwise fill with the average: the materials it draws on
|
|
112
|
+
and how far each can be trusted, the writer's voice scoped to this form, audience and purpose,
|
|
113
|
+
who the piece speaks as, who reads it and what they already know, the one change the piece is
|
|
114
|
+
for, its form, the claims it argues in order, where every fact comes from, and in fiction every
|
|
115
|
+
character who speaks. Findings still report under the nine tests, and `lint` adds one line:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
essay.hyperspec.md: pass (9/9)
|
|
119
|
+
writing: 9/9 blocks complete
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Start one with every block laid out and waiting:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx @supersuit/hyperspec init essay.hyperspec.md --profile writing --title "Your title"
|
|
126
|
+
npx @supersuit/hyperspec init story.hyperspec.md --profile writing --form "short story" --fiction
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The skeleton fails until every placeholder is real and its four open questions (whose voice,
|
|
130
|
+
who speaks, who reads, what changes) are answered. The blocks, every field, and which test
|
|
131
|
+
each rule reports under are in [WRITING.md](WRITING.md). Two complete specs that pass with
|
|
132
|
+
nothing to warn ship in `examples/writing/`: an essay for new managers, and a short story with
|
|
133
|
+
two characters whose voices a judge can tell apart.
|
|
134
|
+
|
|
135
|
+
### Marking materials
|
|
136
|
+
|
|
137
|
+
Every material a writing spec draws on is marked before the spec can pass: split into segments,
|
|
138
|
+
and each segment labeled with what it may be used as (claim, story, quote, stance, question,
|
|
139
|
+
aside, private). hyperspec does the splitting and the checking; an agent or a person does the
|
|
140
|
+
labeling.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npx @supersuit/hyperspec segments init materials/voice-memo.md --id voice-memo
|
|
144
|
+
npx @supersuit/hyperspec lint essay.hyperspec.md
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The first writes `materials/voice-memo.md.segments.jsonl`, every segment `unlabeled`. Name that
|
|
148
|
+
file as `segments:` on the material item, label every segment, and `lint` checks that each label
|
|
149
|
+
is from the set and carries what it needs, that each segment matches the material word for word,
|
|
150
|
+
that the material has not changed since, and that no spine claim cites a private or question
|
|
151
|
+
segment. The file format, the labels, and every finding are in
|
|
152
|
+
[WRITING.md](WRITING.md#marking-materials). A tool of your own can run the same check with
|
|
153
|
+
`import { readSegments } from "@supersuit/hyperspec/writing"`.
|
|
154
|
+
|
|
106
155
|
## The format
|
|
107
156
|
|
|
108
157
|
A hyperspec is a markdown file with a YAML frontmatter block: `decisions`, `requirements`,
|
|
109
|
-
`rejects`, `examples`, `resume`, `feedback`, and `improvement
|
|
110
|
-
standard, including what makes each of the nine tests fail, is in
|
|
158
|
+
`rejects`, `examples`, `resume`, `feedback`, and `improvement`, plus an optional `profile`.
|
|
159
|
+
The full field-by-field standard, including what makes each of the nine tests fail, is in
|
|
160
|
+
[SPEC.md](SPEC.md).
|
|
111
161
|
|
|
112
162
|
## Install
|
|
113
163
|
|
package/SPEC.md
CHANGED
|
@@ -39,6 +39,12 @@ decisions:
|
|
|
39
39
|
source: SPEC.md, section "Recipes"
|
|
40
40
|
author: agent:claude
|
|
41
41
|
chosen_by: agent
|
|
42
|
+
- id: profiles
|
|
43
|
+
state: decided
|
|
44
|
+
value: a profile adds rules for one kind of work under the nine tests; it never adds a tenth test, every finding it raises names one of the nine, and the score stays out of nine
|
|
45
|
+
source: WRITING.md, section "The writing profile"
|
|
46
|
+
author: agent:claude
|
|
47
|
+
chosen_by: agent
|
|
42
48
|
requirements:
|
|
43
49
|
- id: r1
|
|
44
50
|
text: lint exits 0 only when all nine tests pass and nothing is open
|
|
@@ -91,7 +97,7 @@ examples:
|
|
|
91
97
|
- path: examples/minimal.hyperspec.md
|
|
92
98
|
why: the smallest spec that passes all nine tests
|
|
93
99
|
resume:
|
|
94
|
-
next_action: collect adopter issues on 0.
|
|
100
|
+
next_action: collect adopter issues on 0.4, materials marking included, and cut 0.5 from them
|
|
95
101
|
feedback:
|
|
96
102
|
issues: https://github.com/SupersuitUp/hyperspec/issues
|
|
97
103
|
fork: MIT; fork it for your own purposes and say so in your SPEC
|
|
@@ -103,7 +109,7 @@ improvement:
|
|
|
103
109
|
|
|
104
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.
|
|
105
111
|
|
|
106
|
-
**Version 0.
|
|
112
|
+
**Version 0.4.0** (2026-09-29)
|
|
107
113
|
|
|
108
114
|
## What makes a spec a hyperspec
|
|
109
115
|
|
|
@@ -161,7 +167,7 @@ decisions:
|
|
|
161
167
|
state: decided # decided | delegated | open
|
|
162
168
|
value: the operator, reading on a phone
|
|
163
169
|
source: interview A2 # where it came from
|
|
164
|
-
author:
|
|
170
|
+
author: example-author # a person slug, or agent:<model>
|
|
165
171
|
chosen_by: human # human | agent
|
|
166
172
|
- id: length
|
|
167
173
|
state: delegated
|
|
@@ -182,7 +188,7 @@ requirements:
|
|
|
182
188
|
check:
|
|
183
189
|
rubric: ask the simulated reader to define the term; pass only on a correct definition
|
|
184
190
|
source: design doc, audience block
|
|
185
|
-
author:
|
|
191
|
+
author: example-author
|
|
186
192
|
rejects:
|
|
187
193
|
- hype words about AI
|
|
188
194
|
examples:
|
|
@@ -200,7 +206,7 @@ improvement:
|
|
|
200
206
|
|
|
201
207
|
## The test-to-field map
|
|
202
208
|
|
|
203
|
-
Each row lists every condition under which `hyperspec lint` fails that test. A warning never fails a test. A value that is only a YAML comment (`source: # TODO`),
|
|
209
|
+
Each row lists every condition under which `hyperspec lint` fails that test. A warning never fails a test. A value that is only a YAML comment (`source: # TODO`), `null`, `~`, or a placeholder counts as missing. A placeholder is a whole value, trimmed and in any case, of `todo`, `tbd`, `fixme`, `xxx`, `placeholder`, `<placeholder>`, `n/a`, a run of dashes, a run of question marks, or an ellipsis, optionally followed by a trailing `.`, `:` or `!`. Real text that starts with one of those (`TODO: write the opening`) counts as present, and so do `none` and a quoted value that happens to start with `#` (`source: "# literal"`).
|
|
204
210
|
|
|
205
211
|
| Test | Fails when |
|
|
206
212
|
|---|---|
|
|
@@ -210,10 +216,12 @@ Each row lists every condition under which `hyperspec lint` fails that test. A w
|
|
|
210
216
|
| 4 every field says where it came from and who wrote it | a decision or requirement without `source` or `author`; a decision whose `chosen_by` is not human or agent |
|
|
211
217
|
| 5 negative space is specified | `rejects` missing or empty; a `rejects` item that is not a plain string |
|
|
212
218
|
| 6 examples outrank adjectives | `examples` missing or empty; an example without `path` or `why`; a `path` that is not an http(s) URL and does not exist, is a folder, or is the spec itself, read relative to the spec or as an absolute path |
|
|
213
|
-
| 7 a stranger can resume it | `resume.next_action` missing; a `next_action` that is only a no-action word (`continue`, `follow up`, `tbd`, `todo`, `keep going`, `pick it back up`, `n/a`, `none`); a `next_action` that says `as discussed` or `as mentioned earlier` or `above`. Those pointers in the body are a warning, and so
|
|
219
|
+
| 7 a stranger can resume it | `resume.next_action` missing; a `next_action` that is only a no-action word (`continue`, `follow up`, `tbd`, `todo`, `keep going`, `pick it back up`, `n/a`, `none`); a `next_action` that says `as discussed` or `as mentioned earlier` or `above`. Those pointers in the body are a warning, and so are a `hyperspec` version and a `profile` this linter does not know |
|
|
214
220
|
| 8 its adopters can push back on it | `feedback.issues` or `feedback.fork` missing |
|
|
215
221
|
| 9 it improves itself | `improvement.ledger` missing; a ledger path that exists and is not a readable file; if the ledger file exists, a line that is not a JSON object, a `verdict` outside one-shot, improved or not-improved, `improved` without `change`, `not-improved` without `reason`. A declared ledger that does not exist yet is a warning |
|
|
216
222
|
|
|
223
|
+
A profile adds its own conditions to these rows. The writing profile's are in [WRITING.md](WRITING.md#the-test-mapping), including the checks on each material's segments file: every material marked, every segment labeled from the closed set and matching its material word for word, the marking current (tests 1 and 4), and no spine claim citing a private or question segment (test 5).
|
|
224
|
+
|
|
217
225
|
## Exit codes
|
|
218
226
|
|
|
219
227
|
`hyperspec lint` reports the worst result across every file it is given:
|
|
@@ -233,6 +241,12 @@ Every run of a skill that works from a hyperspec writes one line to the ledger n
|
|
|
233
241
|
|
|
234
242
|
Silence is not a verdict. A run that learned nothing has to say so and why, and a ledger line with none of the three verdicts fails the ninth test.
|
|
235
243
|
|
|
244
|
+
## Profiles
|
|
245
|
+
|
|
246
|
+
A profile adds the rules for one kind of work on top of the nine tests. A spec opts in with a top-level `profile:` naming it. A profile never adds a tenth test: every finding it raises reports under one of the nine, with an id that starts with the profile's name, and the score stays out of nine. `lint` prints one more line for a profiled spec, how many of the profile's blocks are complete. A `profile` this linter does not know is a warning under test 7, and none of its rules are checked.
|
|
247
|
+
|
|
248
|
+
One profile ships: `writing`, for essays, chapters, letters, stories and anything else an agent drafts for a person to read. Its blocks, its fields, which test each rule reports under, `hyperspec init --profile writing`, and marking materials with `hyperspec segments init` are in [WRITING.md](WRITING.md).
|
|
249
|
+
|
|
236
250
|
## Recipes
|
|
237
251
|
|
|
238
252
|
A hyperspec says what the work must be. A recipe records what one output was made from, so the output can be checked, made again with one change, and graded against the version before it. hyperspec never calls a model. Every step that needs one is a command the caller supplies: a runner for stages and a doctor for grading.
|
|
@@ -246,8 +260,8 @@ A recipe sits beside its output as `<output>.recipe.json`: JSON with a two-space
|
|
|
246
260
|
"recipe": "0.1",
|
|
247
261
|
"created": "2026-09-28T18:00:00.000Z",
|
|
248
262
|
"output": { "path": "essay.md", "sha256": "<hex>" },
|
|
249
|
-
"factory": { "name": "
|
|
250
|
-
"spec": { "path": "essay.hyperspec.md", "sha256": "<hex>", "authors": { "audience": "
|
|
263
|
+
"factory": { "name": "my-factory", "version": "0.3.0" },
|
|
264
|
+
"spec": { "path": "essay.hyperspec.md", "sha256": "<hex>", "authors": { "audience": "example-author", "length": "agent:claude" } },
|
|
251
265
|
"inputs": [
|
|
252
266
|
{ "name": "call", "path": "materials/call.md", "sha256": "<hex>", "order": 1 }
|
|
253
267
|
],
|
|
@@ -261,8 +275,8 @@ A recipe sits beside its output as `<output>.recipe.json`: JSON with a two-space
|
|
|
261
275
|
"verdict": { "station": "outline-has-claim-chain", "pass": true, "note": "" }
|
|
262
276
|
}
|
|
263
277
|
],
|
|
264
|
-
"clicker": "
|
|
265
|
-
"approver": "
|
|
278
|
+
"clicker": "example-author",
|
|
279
|
+
"approver": "example-author",
|
|
266
280
|
"parent": null,
|
|
267
281
|
"change": null
|
|
268
282
|
}
|