@supersuit/hyperspec 0.1.0 → 0.3.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 +102 -0
- package/README.md +105 -4
- package/SPEC.md +205 -13
- package/WRITING.md +426 -0
- package/bin/hyperspec.mjs +322 -2
- package/examples/minimal.hyperspec.md +2 -2
- package/examples/recipe/doctor.mjs +11 -0
- package/examples/recipe/essay.hyperspec.md +50 -0
- package/examples/recipe/factory.mjs +29 -0
- package/examples/recipe/materials/call-2.md +2 -0
- package/examples/recipe/materials/call.md +3 -0
- package/examples/recipe/materials/notes.md +3 -0
- package/examples/recipe/runner.mjs +20 -0
- package/examples/recipe/runs.jsonl +0 -0
- package/examples/recipe/stages.mjs +31 -0
- 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/team-survey.md +7 -0
- package/examples/writing/essay/materials/voice-memo.md +18 -0
- package/examples/writing/essay/runs.jsonl +0 -0
- package/examples/writing/essay.hyperspec.md +217 -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 +8 -0
- package/examples/writing/story/materials/notes.md +14 -0
- package/examples/writing/story/materials/scene-list.md +7 -0
- package/examples/writing/story/runs.jsonl +0 -0
- package/examples/writing/story.hyperspec.md +285 -0
- package/examples/writing/style-rules.md +19 -0
- package/package.json +8 -2
- package/runs.jsonl +0 -0
- package/src/blobs.mjs +77 -0
- package/src/compare.mjs +189 -0
- package/src/fsutil.mjs +33 -0
- package/src/hash.mjs +26 -0
- package/src/placeholder.mjs +20 -0
- package/src/profiles.mjs +50 -0
- package/src/recipe.mjs +164 -0
- package/src/regenerate.mjs +318 -0
- package/src/reproduce.mjs +156 -0
- package/src/rules.mjs +58 -19
- package/src/score.mjs +7 -1
- package/src/template.mjs +15 -3
- package/src/writer.mjs +125 -0
- package/src/writing-fields.mjs +317 -0
- package/src/writing-template.mjs +192 -0
- package/src/writing.mjs +181 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
---
|
|
2
|
+
hyperspec: "0.1"
|
|
3
|
+
title: Hand your first one-on-one to the person you manage
|
|
4
|
+
kind: essay
|
|
5
|
+
profile: writing
|
|
6
|
+
decisions:
|
|
7
|
+
- id: kind
|
|
8
|
+
state: decided
|
|
9
|
+
value: an essay of 700 to 1,100 words for a newsletter read by people in their first year of managing
|
|
10
|
+
source: essay/materials/voice-memo.md
|
|
11
|
+
author: example-author
|
|
12
|
+
chosen_by: human
|
|
13
|
+
- id: agenda-card
|
|
14
|
+
state: decided
|
|
15
|
+
value: the essay ends on the three questions, written so a reader can copy them onto a card
|
|
16
|
+
source: essay/materials/voice-memo.md, the three questions
|
|
17
|
+
author: example-author
|
|
18
|
+
chosen_by: human
|
|
19
|
+
- id: publish-venue
|
|
20
|
+
state: delegated
|
|
21
|
+
rule: publish where the audience block's reads_on line says the reader already is, and nowhere else
|
|
22
|
+
source: publishing checklist
|
|
23
|
+
author: agent:claude
|
|
24
|
+
chosen_by: agent
|
|
25
|
+
requirements:
|
|
26
|
+
- id: r1
|
|
27
|
+
text: the opening line tells the reader who sets the agenda of a first one-on-one
|
|
28
|
+
fails_when: a reader shown only the first two sentences cannot say who should set the agenda
|
|
29
|
+
check:
|
|
30
|
+
rubric: show the simulated reader the first two sentences and ask who sets the agenda; pass only on "the report"
|
|
31
|
+
source: essay/goldens/opening.md
|
|
32
|
+
author: example-author
|
|
33
|
+
- id: r2
|
|
34
|
+
text: the three questions appear word for word as the voice memo states them
|
|
35
|
+
fails_when: any of the three questions differs from essay/materials/voice-memo.md by a word
|
|
36
|
+
check:
|
|
37
|
+
station: verbatim match of each question against the voice memo
|
|
38
|
+
source: essay/materials/voice-memo.md
|
|
39
|
+
author: example-author
|
|
40
|
+
- id: r3
|
|
41
|
+
text: every survey figure in the draft matches the verified survey summary
|
|
42
|
+
fails_when: a figure in the draft has no entry in the claims ledger pointing at essay/materials/team-survey.md, or differs from it
|
|
43
|
+
check:
|
|
44
|
+
station: every factual claim in the ledger points at a source span
|
|
45
|
+
source: sourcing pass
|
|
46
|
+
author: agent:claude
|
|
47
|
+
- id: r4
|
|
48
|
+
text: the draft argues only the four claims in the spine, in order
|
|
49
|
+
fails_when: a paragraph advances a point that traces to none of c1 to c4, or c3 lands before c2
|
|
50
|
+
check:
|
|
51
|
+
rubric: map each paragraph to a spine claim; fail on any paragraph that maps to none or out of order
|
|
52
|
+
source: spine interview
|
|
53
|
+
author: example-author
|
|
54
|
+
- id: r5
|
|
55
|
+
text: the draft tells the reader what to do with silence in the meeting
|
|
56
|
+
fails_when: the draft never says to wait after asking a question
|
|
57
|
+
check:
|
|
58
|
+
rubric: ask the simulated reader what to do after asking the first question; pass only on "wait"
|
|
59
|
+
source: essay/materials/interview-notes.md
|
|
60
|
+
author: example-author
|
|
61
|
+
- id: r6
|
|
62
|
+
text: the draft stays inside its length envelope
|
|
63
|
+
fails_when: the word count is under 700 or over 1,100
|
|
64
|
+
check:
|
|
65
|
+
station: word count against form.length
|
|
66
|
+
source: form decision
|
|
67
|
+
author: example-author
|
|
68
|
+
rejects:
|
|
69
|
+
- a list of more than three questions
|
|
70
|
+
- advice to use the one-on-one for project status
|
|
71
|
+
- any claim about what most managers do that the survey does not support
|
|
72
|
+
- the walking one-on-one aside from the voice memo
|
|
73
|
+
examples:
|
|
74
|
+
- path: essay/goldens/opening.md
|
|
75
|
+
why: the claim lands in the first sentence, and the second sentence turns it into an instruction
|
|
76
|
+
resume:
|
|
77
|
+
next_action: mark the three materials into labeled segments, then outline the four spine claims against the form's required parts
|
|
78
|
+
feedback:
|
|
79
|
+
issues: https://github.com/SupersuitUp/hyperspec/issues
|
|
80
|
+
fork: MIT; fork it for your own purposes
|
|
81
|
+
improvement:
|
|
82
|
+
ledger: essay/runs.jsonl
|
|
83
|
+
writing:
|
|
84
|
+
materials:
|
|
85
|
+
items:
|
|
86
|
+
- id: voice-memo
|
|
87
|
+
path: essay/materials/voice-memo.md
|
|
88
|
+
produced_by: example-author
|
|
89
|
+
captured: "2026-09-12"
|
|
90
|
+
how: voice memo, transcribed
|
|
91
|
+
trust: raw
|
|
92
|
+
- id: interview
|
|
93
|
+
path: essay/materials/interview-notes.md
|
|
94
|
+
produced_by: example-author
|
|
95
|
+
captured: "2026-09-15"
|
|
96
|
+
how: notes taken during a call, reviewed by the person interviewed
|
|
97
|
+
trust: considered
|
|
98
|
+
- id: survey
|
|
99
|
+
path: essay/materials/team-survey.md
|
|
100
|
+
produced_by: example-author
|
|
101
|
+
captured: "2026-05-30"
|
|
102
|
+
how: survey summary, figures checked against the raw export by a second person
|
|
103
|
+
trust: verified
|
|
104
|
+
check:
|
|
105
|
+
station: every segment of every material carries a label from the closed set
|
|
106
|
+
source: capture step
|
|
107
|
+
author: agent:claude
|
|
108
|
+
dna:
|
|
109
|
+
writer: example-author
|
|
110
|
+
scope:
|
|
111
|
+
form: essay
|
|
112
|
+
audience: new managers
|
|
113
|
+
purpose: teach
|
|
114
|
+
rules: style-rules.md
|
|
115
|
+
goldens:
|
|
116
|
+
- path: essay/goldens/opening.md
|
|
117
|
+
why: one plain claim, then a second sentence that turns it into something to do
|
|
118
|
+
- path: essay/goldens/close.md
|
|
119
|
+
why: ends on an instruction and gives the reason for it in the same sentence
|
|
120
|
+
check:
|
|
121
|
+
rubric: blind lineup within this scope; a judge shown the generated opening beside the two goldens cannot pick it out
|
|
122
|
+
source: goldens marked on the review page
|
|
123
|
+
author: example-author
|
|
124
|
+
persona:
|
|
125
|
+
identity: self
|
|
126
|
+
stance: mentor
|
|
127
|
+
may_assert:
|
|
128
|
+
- what the author did in their own first one-on-ones and what happened
|
|
129
|
+
- the three questions the author uses now
|
|
130
|
+
will_not_say:
|
|
131
|
+
- a claim about what most managers do, beyond the survey's own figures
|
|
132
|
+
- the name of anyone on the author's team
|
|
133
|
+
facts_from: sources
|
|
134
|
+
check:
|
|
135
|
+
rubric: persona-consistency judge; the mentor stance holds, and no fact appears that is not in the claims ledger
|
|
136
|
+
source: persona interview
|
|
137
|
+
author: example-author
|
|
138
|
+
audience:
|
|
139
|
+
who: someone in their first three months of managing, who was promoted from the team they now lead
|
|
140
|
+
funnel_now: has a first one-on-one with a new report on the calendar this week
|
|
141
|
+
knows:
|
|
142
|
+
- one-on-one
|
|
143
|
+
- report
|
|
144
|
+
- tracker
|
|
145
|
+
believes_now: a one-on-one is where a manager catches up on how the work is going
|
|
146
|
+
wants: a plan for the first meeting that will not waste either person's half hour
|
|
147
|
+
reads_on: a phone, in the ten minutes before the meeting
|
|
148
|
+
reader: person
|
|
149
|
+
check:
|
|
150
|
+
station: term check against knows; any other term is defined on first use
|
|
151
|
+
rubric: simulated reader reports where it got lost and where it stopped reading
|
|
152
|
+
source: audience interview
|
|
153
|
+
author: example-author
|
|
154
|
+
goal:
|
|
155
|
+
from: plans to run the first one-on-one from their own list
|
|
156
|
+
to: hands the first one-on-one to the report and asks the three questions
|
|
157
|
+
next_if_worked: copies the three questions into their calendar invite
|
|
158
|
+
change:
|
|
159
|
+
kind: action
|
|
160
|
+
text: the reader asks the three questions in their next one-on-one and waits after each
|
|
161
|
+
conditions: [r1, r2, r3, r4, r5, r6]
|
|
162
|
+
check:
|
|
163
|
+
rubric: the doctor grades the draft against every condition; the simulated reader is asked whether it would copy the questions now
|
|
164
|
+
source: goal interview
|
|
165
|
+
author: example-author
|
|
166
|
+
form:
|
|
167
|
+
name: essay
|
|
168
|
+
length:
|
|
169
|
+
min: 700
|
|
170
|
+
max: 1100
|
|
171
|
+
unit: words
|
|
172
|
+
required_parts:
|
|
173
|
+
- an opening that states the claim
|
|
174
|
+
- the story of the author's first one-on-one
|
|
175
|
+
- the three questions
|
|
176
|
+
- what to do with the answers
|
|
177
|
+
- a close the reader can act on
|
|
178
|
+
stations:
|
|
179
|
+
- the three questions render as a numbered list
|
|
180
|
+
check:
|
|
181
|
+
station: structure and length check against required_parts and length
|
|
182
|
+
source: form decision
|
|
183
|
+
author: example-author
|
|
184
|
+
spine:
|
|
185
|
+
kind: primer
|
|
186
|
+
claims:
|
|
187
|
+
- id: c1
|
|
188
|
+
text: the first one-on-one is the one meeting where the report should set the agenda
|
|
189
|
+
materials: [voice-memo, interview]
|
|
190
|
+
- id: c2
|
|
191
|
+
text: status belongs in the tracker, and a one-on-one spent on it teaches the manager nothing new
|
|
192
|
+
materials: [voice-memo, survey]
|
|
193
|
+
- id: c3
|
|
194
|
+
text: three questions are enough to hand the meeting over
|
|
195
|
+
materials: [voice-memo]
|
|
196
|
+
- id: c4
|
|
197
|
+
text: the answer worth having comes after a silence the manager does not fill
|
|
198
|
+
materials: [interview]
|
|
199
|
+
check:
|
|
200
|
+
rubric: each claim lands, in order, and the draft argues nothing outside the chain
|
|
201
|
+
source: spine interview
|
|
202
|
+
author: example-author
|
|
203
|
+
sources:
|
|
204
|
+
ledger: essay/claims.jsonl
|
|
205
|
+
unsourced_claim: fail
|
|
206
|
+
check:
|
|
207
|
+
station: every factual claim in the ledger points at a source span; every quote matches its source verbatim
|
|
208
|
+
source: sourcing pass
|
|
209
|
+
author: agent:claude
|
|
210
|
+
fiction: false
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
# Hand your first one-on-one to the person you manage
|
|
214
|
+
|
|
215
|
+
A worked example of the writing profile: an essay for new managers, specified before a word of
|
|
216
|
+
it is drafted. Every file this spec names ships beside it. `essay/claims.jsonl` does not exist
|
|
217
|
+
yet, because the claims ledger is written during drafting.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Notes from a morning spent at a working bakery, 3:30 to 7:30, with the owner's permission.
|
|
2
|
+
Considered: the owner read these notes and corrected the proofing times.
|
|
3
|
+
|
|
4
|
+
- First mix at 3:45. Rye sourdough proofs about three hours at room temperature in winter.
|
|
5
|
+
- Trays are turned halfway through the bake because the back left of a deck oven runs hot.
|
|
6
|
+
- The doors open to customers at 7:00. The first person in is usually a regular.
|
|
7
|
+
- The owner does not talk while shaping. Talking happens at the mixer and at the till.
|
|
8
|
+
- Flour is weighed, never scooped. Water temperature is checked every batch.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Author's notes for the story, typed over two evenings. Raw thinking.
|
|
2
|
+
|
|
3
|
+
A bakery on its last morning before the sale closes. Two people: the owner, who has run it for
|
|
4
|
+
thirty-one years, and the apprentice she took on at sixteen, now nineteen. She has not told him
|
|
5
|
+
it is sold. He has not told her he got into a baking school in another city and leaves in the
|
|
6
|
+
autumn. Each thinks they are protecting the other.
|
|
7
|
+
|
|
8
|
+
The story is four scenes, one morning, 3:40 to 7:00. The oven has a noise. The rye is the thing
|
|
9
|
+
she has never let him do alone.
|
|
10
|
+
|
|
11
|
+
What it is about, I think: a craft outlives the room it was practiced in. And: people who love
|
|
12
|
+
each other in a work setting say it through the work.
|
|
13
|
+
|
|
14
|
+
The last line should be about bread, never about feelings.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Scene list, agreed with the editor before drafting. Considered.
|
|
2
|
+
|
|
3
|
+
- scene-1, 3:40: Theo arrives. Ines is already mixing. The oven noise. No one says anything real.
|
|
4
|
+
- scene-2, 4:30: shaping. Ines lets Theo shape the rye for the first time, and does not say why.
|
|
5
|
+
- scene-3, 5:50: the bake. Ines tells Theo the bakery is sold, while turning a tray.
|
|
6
|
+
- scene-4, 6:55: before the doors open. Theo tells Ines about the school. She hands him the
|
|
7
|
+
starter jar.
|
|
File without changes
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
---
|
|
2
|
+
hyperspec: "0.1"
|
|
3
|
+
title: The Rye
|
|
4
|
+
kind: short story
|
|
5
|
+
profile: writing
|
|
6
|
+
decisions:
|
|
7
|
+
- id: point-of-view
|
|
8
|
+
state: decided
|
|
9
|
+
value: first person, told by Theo, in the past tense
|
|
10
|
+
source: story/materials/notes.md
|
|
11
|
+
author: example-author
|
|
12
|
+
chosen_by: human
|
|
13
|
+
- id: time-span
|
|
14
|
+
state: decided
|
|
15
|
+
value: one morning, 3:40 to 7:00, in the four scenes of the scene list and in that order
|
|
16
|
+
source: story/materials/scene-list.md
|
|
17
|
+
author: example-author
|
|
18
|
+
chosen_by: human
|
|
19
|
+
- id: bakery-name
|
|
20
|
+
state: delegated
|
|
21
|
+
rule: the bakery is never named; it is always "the bakery" or "Ines's"
|
|
22
|
+
source: editor's note on the scene list
|
|
23
|
+
author: agent:claude
|
|
24
|
+
chosen_by: agent
|
|
25
|
+
requirements:
|
|
26
|
+
- id: r1
|
|
27
|
+
text: the two voices cannot be confused
|
|
28
|
+
fails_when: a judge shown ten lines of dialogue with the speaker hidden names the wrong speaker for more than one of them
|
|
29
|
+
check:
|
|
30
|
+
rubric: blind attribution test across both characters, ten lines drawn at random from the draft
|
|
31
|
+
source: story/materials/notes.md
|
|
32
|
+
author: example-author
|
|
33
|
+
- id: r2
|
|
34
|
+
text: no one says what they could not know yet
|
|
35
|
+
fails_when: Theo mentions the sale before scene-3, or Ines mentions the school before scene-4
|
|
36
|
+
check:
|
|
37
|
+
station: knowledge-leak check of every line against each character's knowledge timeline
|
|
38
|
+
source: story/materials/scene-list.md
|
|
39
|
+
author: example-author
|
|
40
|
+
- id: r3
|
|
41
|
+
text: the bakery's process matches a real working morning
|
|
42
|
+
fails_when: a proofing time, the tray turn, or the opening time differs from story/materials/bakery-visit.md
|
|
43
|
+
check:
|
|
44
|
+
station: every process detail in the claims ledger points at a span of the bakery visit notes
|
|
45
|
+
source: story/materials/bakery-visit.md
|
|
46
|
+
author: agent:claude
|
|
47
|
+
- id: r4
|
|
48
|
+
text: the story is the four scenes of the scene list, in order
|
|
49
|
+
fails_when: the draft has more or fewer than four scenes, or they run out of the scene list's order
|
|
50
|
+
check:
|
|
51
|
+
station: structure check against story/materials/scene-list.md
|
|
52
|
+
source: story/materials/scene-list.md
|
|
53
|
+
author: example-author
|
|
54
|
+
- id: r5
|
|
55
|
+
text: the last line is about bread
|
|
56
|
+
fails_when: the final sentence names a feeling, or does not mention bread, dough, flour or the starter
|
|
57
|
+
check:
|
|
58
|
+
rubric: read the final sentence; fail if it names an emotion or mentions none of bread, dough, flour or the starter
|
|
59
|
+
source: story/materials/notes.md
|
|
60
|
+
author: example-author
|
|
61
|
+
- id: r6
|
|
62
|
+
text: the story stays inside its length envelope
|
|
63
|
+
fails_when: the word count is under 2,500 or over 4,000
|
|
64
|
+
check:
|
|
65
|
+
station: word count against form.length
|
|
66
|
+
source: editor's brief
|
|
67
|
+
author: example-author
|
|
68
|
+
rejects:
|
|
69
|
+
- either character saying out loud that they love or will miss the other
|
|
70
|
+
- a flashback outside the one morning
|
|
71
|
+
- a scene where the oven noise is explained
|
|
72
|
+
- an ending that tells the reader what the starter jar means
|
|
73
|
+
examples:
|
|
74
|
+
- path: story/goldens/dialogue.md
|
|
75
|
+
why: three short lines carry a ritual both characters know, without either of them naming it
|
|
76
|
+
resume:
|
|
77
|
+
next_action: mark the three materials into labeled segments, then draft scene-1 from the scene list with Theo's knowledge as of scene-1
|
|
78
|
+
feedback:
|
|
79
|
+
issues: https://github.com/SupersuitUp/hyperspec/issues
|
|
80
|
+
fork: MIT; fork it for your own purposes
|
|
81
|
+
improvement:
|
|
82
|
+
ledger: story/runs.jsonl
|
|
83
|
+
writing:
|
|
84
|
+
materials:
|
|
85
|
+
items:
|
|
86
|
+
- id: notes
|
|
87
|
+
path: story/materials/notes.md
|
|
88
|
+
produced_by: example-author
|
|
89
|
+
captured: "2026-08-20"
|
|
90
|
+
how: typed notes
|
|
91
|
+
trust: raw
|
|
92
|
+
- id: bakery-visit
|
|
93
|
+
path: story/materials/bakery-visit.md
|
|
94
|
+
produced_by: example-author
|
|
95
|
+
captured: "2026-08-28"
|
|
96
|
+
how: notes taken on site, corrected by the bakery owner afterwards
|
|
97
|
+
trust: considered
|
|
98
|
+
- id: scene-list
|
|
99
|
+
path: story/materials/scene-list.md
|
|
100
|
+
produced_by: example-author
|
|
101
|
+
captured: "2026-09-02"
|
|
102
|
+
how: scene list agreed with the editor
|
|
103
|
+
trust: considered
|
|
104
|
+
check:
|
|
105
|
+
station: every segment of every material carries a label from the closed set
|
|
106
|
+
source: capture step
|
|
107
|
+
author: agent:claude
|
|
108
|
+
dna:
|
|
109
|
+
writer: example-author
|
|
110
|
+
scope:
|
|
111
|
+
form: short story
|
|
112
|
+
audience: literary magazine readers
|
|
113
|
+
purpose: move
|
|
114
|
+
rules: style-rules.md
|
|
115
|
+
goldens:
|
|
116
|
+
- path: story/goldens/opening.md
|
|
117
|
+
why: the narrator's voice arrives through one concrete sound, and the sentence about the mixer tells you how he sees the world
|
|
118
|
+
- path: story/goldens/dialogue.md
|
|
119
|
+
why: the narration between lines says only what Theo notices, never what Ines feels
|
|
120
|
+
check:
|
|
121
|
+
rubric: blind lineup within this scope; a judge shown a generated passage beside the two goldens cannot pick it out
|
|
122
|
+
source: goldens marked on the review page
|
|
123
|
+
author: example-author
|
|
124
|
+
persona:
|
|
125
|
+
identity: character:theo
|
|
126
|
+
stance: witness
|
|
127
|
+
may_assert:
|
|
128
|
+
- what Theo sees, hears and does in the bakery that morning
|
|
129
|
+
- what Theo knows as of the current scene, per his knowledge timeline
|
|
130
|
+
will_not_say:
|
|
131
|
+
- what Ines is thinking or feeling
|
|
132
|
+
- anything Theo does not know yet at that point in the morning
|
|
133
|
+
facts_from: sources
|
|
134
|
+
check:
|
|
135
|
+
rubric: persona-consistency judge; Theo's narration stays a witness's, and no process detail appears that is not in the claims ledger
|
|
136
|
+
source: persona interview
|
|
137
|
+
author: example-author
|
|
138
|
+
audience:
|
|
139
|
+
who: readers of a quarterly literary magazine who read short fiction in print
|
|
140
|
+
funnel_now: has turned to the story in the magazine without knowing the author
|
|
141
|
+
knows:
|
|
142
|
+
- bakery
|
|
143
|
+
- apprentice
|
|
144
|
+
- sourdough
|
|
145
|
+
believes_now: nothing yet about this author or these two people
|
|
146
|
+
wants: a story they can finish in one sitting and keep thinking about
|
|
147
|
+
reads_on: print, in one sitting of about fifteen minutes
|
|
148
|
+
reader: person
|
|
149
|
+
check:
|
|
150
|
+
station: term check against knows; proof, starter and deck oven are made plain by context on first use
|
|
151
|
+
rubric: simulated reader reports where it got lost and where it stopped reading
|
|
152
|
+
source: editor's brief
|
|
153
|
+
author: example-author
|
|
154
|
+
goal:
|
|
155
|
+
from: has never read the author
|
|
156
|
+
to: finishes the story and remembers the starter jar
|
|
157
|
+
next_if_worked: looks for the author's other stories
|
|
158
|
+
change:
|
|
159
|
+
kind: feeling
|
|
160
|
+
text: the reader feels the handover of the starter jar as the moment the two say what neither says aloud
|
|
161
|
+
conditions: [r1, r2, r3, r4, r5, r6]
|
|
162
|
+
check:
|
|
163
|
+
rubric: the doctor grades the draft against every condition; the simulated reader is asked what the starter jar meant and whether it would look for the author's next story
|
|
164
|
+
source: goal interview
|
|
165
|
+
author: example-author
|
|
166
|
+
form:
|
|
167
|
+
name: short story
|
|
168
|
+
length:
|
|
169
|
+
min: 2500
|
|
170
|
+
max: 4000
|
|
171
|
+
unit: words
|
|
172
|
+
required_parts:
|
|
173
|
+
- four scenes, in the scene list's order
|
|
174
|
+
- a last line about bread
|
|
175
|
+
stations:
|
|
176
|
+
- continuity against the scene list
|
|
177
|
+
- knowledge-leak check, per character, per scene
|
|
178
|
+
check:
|
|
179
|
+
station: structure and length check against required_parts and length
|
|
180
|
+
source: editor's brief
|
|
181
|
+
author: example-author
|
|
182
|
+
spine:
|
|
183
|
+
kind: story
|
|
184
|
+
claims:
|
|
185
|
+
- id: c1
|
|
186
|
+
text: each of them hides their news to protect the other, and the hiding is the thing they share
|
|
187
|
+
materials: [notes, scene-list]
|
|
188
|
+
- id: c2
|
|
189
|
+
text: people who love each other at work say it through the work
|
|
190
|
+
materials: [notes, bakery-visit]
|
|
191
|
+
- id: c3
|
|
192
|
+
text: a craft outlives the room it was practiced in
|
|
193
|
+
materials: [notes, scene-list]
|
|
194
|
+
check:
|
|
195
|
+
rubric: each claim lands, in order, through what the characters do, and the story argues nothing outside the chain
|
|
196
|
+
source: spine interview
|
|
197
|
+
author: example-author
|
|
198
|
+
sources:
|
|
199
|
+
ledger: story/claims.jsonl
|
|
200
|
+
unsourced_claim: fail
|
|
201
|
+
check:
|
|
202
|
+
station: every process detail in the ledger points at a source span; every quote matches its source verbatim
|
|
203
|
+
source: sourcing pass
|
|
204
|
+
author: agent:claude
|
|
205
|
+
characters:
|
|
206
|
+
- id: ines
|
|
207
|
+
speech:
|
|
208
|
+
uses:
|
|
209
|
+
- instructions in the imperative
|
|
210
|
+
- numbers, weights and times
|
|
211
|
+
- first names, for customers
|
|
212
|
+
never:
|
|
213
|
+
- an apology in words
|
|
214
|
+
- a sentence about her own feelings
|
|
215
|
+
- a question she does not need answered
|
|
216
|
+
rhythm: short, flat sentences, often without a subject; silence where another person would reassure
|
|
217
|
+
wants: to leave the bakery in hands she trusts, without having to say that she is leaving it
|
|
218
|
+
fears: that Theo will stay in a trade with no bakery to do it in, because of her
|
|
219
|
+
hides: that she sold the bakery two weeks ago
|
|
220
|
+
knowledge:
|
|
221
|
+
- by: scene-1
|
|
222
|
+
knows: the sale closes on Friday, and the new owners will not keep it a bakery
|
|
223
|
+
- by: scene-4
|
|
224
|
+
knows: Theo has a place at a baking school in another city and leaves in the autumn
|
|
225
|
+
relationships:
|
|
226
|
+
- to: theo
|
|
227
|
+
how: gives him instructions where another person would give praise
|
|
228
|
+
- to: customers
|
|
229
|
+
how: warm and unhurried, asks after their families by name
|
|
230
|
+
arc_state: has let go of the bakery, and has not yet let go of Theo
|
|
231
|
+
golden_lines:
|
|
232
|
+
- Flour first. Then you can talk.
|
|
233
|
+
- Left side runs hot. Turn them at eight minutes.
|
|
234
|
+
- Sold means sold. Shape the rye.
|
|
235
|
+
rejected_lines:
|
|
236
|
+
- I'm so sorry I didn't tell you sooner, Theo.
|
|
237
|
+
- This place has been my whole life, you know?
|
|
238
|
+
- Would you like to try the rye today?
|
|
239
|
+
check:
|
|
240
|
+
rubric: blind attribution test, knowledge-leak check against the timeline, consistency against golden and rejected lines
|
|
241
|
+
source: story/materials/notes.md
|
|
242
|
+
author: example-author
|
|
243
|
+
- id: theo
|
|
244
|
+
speech:
|
|
245
|
+
uses:
|
|
246
|
+
- questions he already knows the answer to
|
|
247
|
+
- hedges such as "I mean" and "kind of"
|
|
248
|
+
- a joke when he is nervous
|
|
249
|
+
never:
|
|
250
|
+
- a flat instruction
|
|
251
|
+
- a word of baking jargon he has not heard Ines use first
|
|
252
|
+
rhythm: long sentences that double back on themselves and end in a question
|
|
253
|
+
wants: Ines to tell him he is ready, in words, once
|
|
254
|
+
fears: that leaving for school is a betrayal of the person who taught him
|
|
255
|
+
hides: that he has a place at a baking school in another city and leaves in the autumn
|
|
256
|
+
knowledge:
|
|
257
|
+
- by: scene-1
|
|
258
|
+
knows: the oven has made the noise since March, and he leaves for school in the autumn
|
|
259
|
+
- by: scene-3
|
|
260
|
+
knows: the bakery is sold, and the new owners will not keep it a bakery
|
|
261
|
+
relationships:
|
|
262
|
+
- to: ines
|
|
263
|
+
how: asks questions to keep her talking, and never interrupts her while she shapes
|
|
264
|
+
arc_state: still acting the apprentice while privately already leaving
|
|
265
|
+
golden_lines:
|
|
266
|
+
- Okay but like, if the oven's made that noise since March, is it a noise, or is that just how the oven talks now?
|
|
267
|
+
- I can do the rye. I mean, I think I can do the rye. I did it Tuesday, kind of.
|
|
268
|
+
- So is that a yes, or is that the face you make when it's a yes?
|
|
269
|
+
rejected_lines:
|
|
270
|
+
- Shape the rye.
|
|
271
|
+
- I have been accepted to a culinary program and will be leaving in the autumn.
|
|
272
|
+
- Left side runs hot, turn them early.
|
|
273
|
+
check:
|
|
274
|
+
rubric: blind attribution test, knowledge-leak check against the timeline, consistency against golden and rejected lines
|
|
275
|
+
source: story/materials/notes.md
|
|
276
|
+
author: example-author
|
|
277
|
+
fiction: true
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
# The Rye
|
|
281
|
+
|
|
282
|
+
A worked example of the writing profile for fiction: a short story with two characters, each
|
|
283
|
+
specified well enough that an agent can write their dialogue and a judge can tell them apart.
|
|
284
|
+
Every file this spec names ships beside it. `story/claims.jsonl` does not exist yet, because the
|
|
285
|
+
claims ledger is written during drafting.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Style rules
|
|
2
|
+
|
|
3
|
+
The always-on layer. These apply to everything this writer publishes, whatever the form, the
|
|
4
|
+
audience or the purpose. Goldens change with the scope; these do not.
|
|
5
|
+
|
|
6
|
+
## Never
|
|
7
|
+
|
|
8
|
+
- No em dashes. Use a comma, a colon, or a new sentence.
|
|
9
|
+
- No sentence that sets up a claim only to knock down a weaker one first. State the claim.
|
|
10
|
+
- No adjective standing in for evidence. If a sentence says a thing is important, it shows why.
|
|
11
|
+
- No filler openers ("In today's world", "It goes without saying").
|
|
12
|
+
- No exclamation marks outside quoted dialogue.
|
|
13
|
+
|
|
14
|
+
## Always
|
|
15
|
+
|
|
16
|
+
- The first line of a piece states what it is about.
|
|
17
|
+
- A number carries its source, or it does not go in.
|
|
18
|
+
- A quotation is word for word, or it is not in quotation marks.
|
|
19
|
+
- One idea per paragraph. A paragraph that needs a second idea is two paragraphs.
|
package/package.json
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supersuit/hyperspec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A hyperspec is a spec written for an agent: every decision accounted for, every requirement failable and checked, every field traced. The standard and its linter.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"hyperspec": "bin/hyperspec.mjs"
|
|
8
8
|
},
|
|
9
|
+
"exports": {
|
|
10
|
+
"./recipe": "./src/writer.mjs",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
9
13
|
"files": [
|
|
10
14
|
"bin/",
|
|
11
15
|
"src/",
|
|
12
16
|
"examples/",
|
|
13
17
|
"SPEC.md",
|
|
18
|
+
"WRITING.md",
|
|
19
|
+
"runs.jsonl",
|
|
14
20
|
"README.md",
|
|
15
21
|
"CHANGELOG.md",
|
|
16
22
|
"LICENSE"
|
|
@@ -34,6 +40,6 @@
|
|
|
34
40
|
"outcome-factory"
|
|
35
41
|
],
|
|
36
42
|
"dependencies": {
|
|
37
|
-
"@supersuit/superskill": "^0.2.
|
|
43
|
+
"@supersuit/superskill": "^0.2.2"
|
|
38
44
|
}
|
|
39
45
|
}
|
package/runs.jsonl
ADDED
|
File without changes
|
package/src/blobs.mjs
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { sha256 } from "./hash.mjs";
|
|
5
|
+
|
|
6
|
+
// Resolve the store root: the --store flag, then HYPERSPEC_STORE, then the
|
|
7
|
+
// nearest ancestor of `from` holding .hyperspec/ or .git, else from's own
|
|
8
|
+
// directory. `from` is normally the recipe's directory, but a file path
|
|
9
|
+
// works too (we walk up from its dirname).
|
|
10
|
+
export function storeRoot({ from, store } = {}) {
|
|
11
|
+
if (store) return resolve(store);
|
|
12
|
+
if (process.env.HYPERSPEC_STORE) return resolve(process.env.HYPERSPEC_STORE);
|
|
13
|
+
let dir = resolve(from);
|
|
14
|
+
try { if (statSync(dir).isFile()) dir = dirname(dir); } catch { /* from may not exist yet; treat it as a directory */ }
|
|
15
|
+
let cur = dir;
|
|
16
|
+
while (true) {
|
|
17
|
+
if (existsSync(join(cur, ".hyperspec")) || existsSync(join(cur, ".git"))) return cur;
|
|
18
|
+
const parent = dirname(cur);
|
|
19
|
+
if (parent === cur) return dir; // hit the filesystem root: fall back to the starting directory
|
|
20
|
+
cur = parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// A hash read from a recipe is a claim, and it becomes part of a filesystem path here. Anything
|
|
25
|
+
// but exactly 64 lowercase hex characters is refused before it touches the disk, so a crafted
|
|
26
|
+
// recipe cannot point a read at `../../somewhere`, a device, or a pipe that never closes.
|
|
27
|
+
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
28
|
+
export function isSha256(hex) {
|
|
29
|
+
return typeof hex === "string" && SHA256_HEX.test(hex);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function blobPath(root, hex) {
|
|
33
|
+
if (!isSha256(hex)) {
|
|
34
|
+
const shown = typeof hex === "string" ? JSON.stringify(hex.length > 80 ? `${hex.slice(0, 80)}...` : hex) : String(hex);
|
|
35
|
+
throw new Error(`not a SHA-256 hash (64 lowercase hex characters): ${shown}`);
|
|
36
|
+
}
|
|
37
|
+
return join(root, ".hyperspec", "blobs", hex.slice(0, 2), hex);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Writes only if the blob is absent; an existing blob is left untouched (never rewritten).
|
|
41
|
+
// Atomic: bytes land in a temp file in the same directory first, then a single renameSync
|
|
42
|
+
// (atomic on one filesystem) puts them at the final path. A process killed mid-write leaves
|
|
43
|
+
// only the orphaned temp file, never a partially-written file sitting at the content-addressed
|
|
44
|
+
// path — the failure mode a plain writeFileSync(path, bytes) would otherwise leave behind, and
|
|
45
|
+
// which nothing short of an explicit verifyBlob would ever catch afterward.
|
|
46
|
+
export function putBlob(root, bytes) {
|
|
47
|
+
const hex = sha256(bytes);
|
|
48
|
+
const path = blobPath(root, hex);
|
|
49
|
+
if (existsSync(path)) return hex;
|
|
50
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
51
|
+
const tmp = `${path}.tmp-${process.pid}-${randomBytes(6).toString("hex")}`;
|
|
52
|
+
try {
|
|
53
|
+
writeFileSync(tmp, bytes);
|
|
54
|
+
if (existsSync(path)) { unlinkSync(tmp); return hex; } // another writer won the race; keep theirs
|
|
55
|
+
renameSync(tmp, path);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
try { unlinkSync(tmp); } catch { /* nothing to clean up */ }
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
return hex;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// A malformed hash names no blob, so hasBlob, getBlob and verifyBlob treat it as missing.
|
|
64
|
+
export function hasBlob(root, hex) {
|
|
65
|
+
return isSha256(hex) && existsSync(blobPath(root, hex));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getBlob(root, hex) {
|
|
69
|
+
if (!isSha256(hex)) return null;
|
|
70
|
+
try { return readFileSync(blobPath(root, hex)); } catch { return null; }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// True when the blob exists and its bytes re-hash to hex (catches tampering).
|
|
74
|
+
export function verifyBlob(root, hex) {
|
|
75
|
+
const bytes = getBlob(root, hex);
|
|
76
|
+
return bytes !== null && sha256(bytes) === hex;
|
|
77
|
+
}
|