@supersuit/hyperspec 0.2.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 CHANGED
@@ -1,5 +1,65 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 (2026-09-29)
4
+
5
+ The writing profile. A piece of writing can now carry a hyperspec that names everything an
6
+ agent would otherwise fill with the average: what the piece is made from and how far each
7
+ source can be trusted, whose voice it is for this form, audience and purpose, who it speaks as,
8
+ who reads it, the one change it is for, what kind of thing it is, the claims it argues in order,
9
+ where every fact comes from, and in fiction how each character speaks and what they know by
10
+ each scene. `hyperspec lint` checks all of it under the same nine tests, and
11
+ `hyperspec init --profile writing` lays every block out for you to fill in.
12
+
13
+ **Behavior change for existing specs:** a placeholder now counts as missing, everywhere, in
14
+ specs with no profile too. A placeholder is a whole value, trimmed and in any case, of `todo`,
15
+ `tbd`, `fixme`, `xxx`, `placeholder`, `<placeholder>`, `n/a`, a run of dashes, a run of question
16
+ marks, or an ellipsis, optionally followed by a trailing `.`, `:` or `!`. A spec that passed
17
+ 0.2.0 with `source: TODO` or `source: n/a` on a decision now fails that test. Real text that
18
+ starts with one of those, such as `TODO: write the opening`, still counts as present, and so
19
+ does `none`.
20
+
21
+ - `profile: writing` opts a spec in; its blocks live under a top-level `writing:` map:
22
+ `materials`, `dna`, `persona`, `audience`, `goal`, `form`, `spine`, `sources`, and
23
+ `characters`, which is required when `fiction: true`. Every block carries a `check`, a
24
+ `source` and an `author`. The core format still applies in full.
25
+ - No tenth test. Every writing finding reports under one of the nine, with an id starting
26
+ `writing-`, and the score stays out of nine. `lint` prints one more line,
27
+ `writing: <k>/9 blocks complete`, and `--json` carries it as `profile` on each file.
28
+ - A missing block fails test 1 unless a decision with the id `writing-<block>` defers it:
29
+ `open`, which blocks the spec like any open decision, or `delegated` with a `rule`. A
30
+ deferred block does not count as complete.
31
+ - Progress is never stored: a `writing.progress` key fails test 7, because saved progress goes
32
+ stale the first time a session dies mid-arc. Progress is read off the folder instead.
33
+ - Field rules for every block, each under the test it belongs to: closed sets for `fiction`
34
+ (absent means false), `trust`, `reader`, `change.kind`, the shape of `persona.identity` and
35
+ `unsourced_claim`; unique ids for materials, spine claims and characters; a length envelope
36
+ of whole numbers of at least 1 (test 1); five to ten distinct `goal.conditions` naming real
37
+ requirements, each listed once (test 2); spine claims that name real materials (test 4);
38
+ `persona.facts_from: sources` and a non-empty `will_not_say` (test 5); a `why` on every
39
+ golden, material and golden paths that exist, and golden and rejected lines for every
40
+ character with no line in both (test 6). A repeated id counts once toward a minimum. A
41
+ `stance` outside peer, mentor, witness and guide is a warning.
42
+ - A character needs speech rules (what they say and never say), wants, fears, what they hide,
43
+ an arc state, a knowledge timeline, and golden and rejected lines. `relationships` and a
44
+ pointer to a character `entity` file are optional.
45
+ - `hyperspec init <file> --profile writing [--title T] [--form F] [--fiction]` writes a
46
+ skeleton with every required block in schema order, every field a placeholder, and open
47
+ decisions for the four blocks that need your judgment first (dna, persona, audience, goal).
48
+ It fails lint until the placeholders are replaced. `init` exits 2 with a plain message on a
49
+ `--profile` with no value or one it does not know, `--fiction` or `--form` without
50
+ `--profile writing`, `--kind` with it, and a folder that does not exist.
51
+ - `lint` warns under test 7 on a `profile` it does not know, and checks none of its rules. A
52
+ name every object inherits, such as `constructor`, is an unknown profile like any other.
53
+ - WRITING.md documents the profile: the ten components, the schema with every field, the test
54
+ mapping, the closed sets, the seven materials labels and what each may be used as, and
55
+ deferral. It ships in the package.
56
+ - `examples/writing/` ships two complete specs, an essay and a two-character short story, with
57
+ every file they name. Both pass with no findings, and a test keeps them that way.
58
+ - SPEC.md gains a Profiles section and states the placeholder words in its test-to-field map.
59
+ - The illustrative specs and recipe in SPEC.md and `examples/minimal.hyperspec.md` name a
60
+ placeholder author, `example-author`, and a placeholder factory, `my-factory`.
61
+ - 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.
62
+
3
63
  ## 0.2.0 (2026-09-28)
4
64
 
5
65
  Recipes. Every output a factory makes can now carry a recipe beside it: what made it, from
package/README.md CHANGED
@@ -24,6 +24,7 @@ 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. |
27
28
  | `hyperspec recipe check <output-or-recipe>` | Check that a recipe records everything the standard asks for. |
28
29
  | `hyperspec recipe approve <recipe> --by <slug>` | Record who approved the output. |
29
30
  | `hyperspec reproduce <recipe> [--restore]` | Re-check every hash the recipe recorded. Never runs a model. |
@@ -103,11 +104,39 @@ recipe.finish();
103
104
  The schema, the stage key, the runner and doctor contracts, and every exit code are in
104
105
  [SPEC.md](SPEC.md#recipes).
105
106
 
107
+ ## Writing specs
108
+
109
+ A piece of writing gets its own profile. Add `profile: writing` to a hyperspec and it gains nine
110
+ blocks that name what an agent would otherwise fill with the average: the materials it draws on
111
+ and how far each can be trusted, the writer's voice scoped to this form, audience and purpose,
112
+ who the piece speaks as, who reads it and what they already know, the one change the piece is
113
+ for, its form, the claims it argues in order, where every fact comes from, and in fiction every
114
+ character who speaks. Findings still report under the nine tests, and `lint` adds one line:
115
+
116
+ ```
117
+ essay.hyperspec.md: pass (9/9)
118
+ writing: 9/9 blocks complete
119
+ ```
120
+
121
+ Start one with every block laid out and waiting:
122
+
123
+ ```bash
124
+ npx @supersuit/hyperspec init essay.hyperspec.md --profile writing --title "Your title"
125
+ npx @supersuit/hyperspec init story.hyperspec.md --profile writing --form "short story" --fiction
126
+ ```
127
+
128
+ The skeleton fails until every placeholder is real and its four open questions (whose voice,
129
+ who speaks, who reads, what changes) are answered. The blocks, every field, and which test
130
+ each rule reports under are in [WRITING.md](WRITING.md). Two complete specs that pass with
131
+ nothing to warn ship in `examples/writing/`: an essay for new managers, and a short story with
132
+ two characters whose voices a judge can tell apart.
133
+
106
134
  ## The format
107
135
 
108
136
  A hyperspec is a markdown file with a YAML frontmatter block: `decisions`, `requirements`,
109
- `rejects`, `examples`, `resume`, `feedback`, and `improvement`. The full field-by-field
110
- standard, including what makes each of the nine tests fail, is in [SPEC.md](SPEC.md).
137
+ `rejects`, `examples`, `resume`, `feedback`, and `improvement`, plus an optional `profile`.
138
+ The full field-by-field standard, including what makes each of the nine tests fail, is in
139
+ [SPEC.md](SPEC.md).
111
140
 
112
141
  ## Install
113
142
 
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.2, recipes included, and cut 0.3 from them
100
+ next_action: collect adopter issues on 0.3, the writing profile included, and cut 0.4 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.2.0** (2026-09-28)
112
+ **Version 0.3.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: gary-sheng # a person slug, or agent:<model>
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: gary-sheng
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`), or `null`, or `~`, counts as missing. A quoted value that happens to start with `#` (`source: "# literal"`) is real text and counts as present.
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,7 +216,7 @@ 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 is a `hyperspec` version this linter does not know |
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
 
@@ -233,6 +239,12 @@ Every run of a skill that works from a hyperspec writes one line to the ledger n
233
239
 
234
240
  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
241
 
242
+ ## Profiles
243
+
244
+ 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.
245
+
246
+ 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, and `hyperspec init --profile writing` are in [WRITING.md](WRITING.md).
247
+
236
248
  ## Recipes
237
249
 
238
250
  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 +258,8 @@ A recipe sits beside its output as `<output>.recipe.json`: JSON with a two-space
246
258
  "recipe": "0.1",
247
259
  "created": "2026-09-28T18:00:00.000Z",
248
260
  "output": { "path": "essay.md", "sha256": "<hex>" },
249
- "factory": { "name": "compose-a-piece", "version": "0.3.0" },
250
- "spec": { "path": "essay.hyperspec.md", "sha256": "<hex>", "authors": { "audience": "gary-sheng", "length": "agent:claude" } },
261
+ "factory": { "name": "my-factory", "version": "0.3.0" },
262
+ "spec": { "path": "essay.hyperspec.md", "sha256": "<hex>", "authors": { "audience": "example-author", "length": "agent:claude" } },
251
263
  "inputs": [
252
264
  { "name": "call", "path": "materials/call.md", "sha256": "<hex>", "order": 1 }
253
265
  ],
@@ -261,8 +273,8 @@ A recipe sits beside its output as `<output>.recipe.json`: JSON with a two-space
261
273
  "verdict": { "station": "outline-has-claim-chain", "pass": true, "note": "" }
262
274
  }
263
275
  ],
264
- "clicker": "gary-sheng",
265
- "approver": "gary-sheng",
276
+ "clicker": "example-author",
277
+ "approver": "example-author",
266
278
  "parent": null,
267
279
  "change": null
268
280
  }
package/WRITING.md ADDED
@@ -0,0 +1,426 @@
1
+ # The writing profile
2
+
3
+ A hyperspec for a piece of writing. An essay, a chapter, a letter, a story: anything an agent
4
+ drafts and a person reads.
5
+
6
+ Writing is where the average does the most damage. Asked for "an essay on X", an agent fills
7
+ every gap you left with the most typical choice: the typical reader, the typical argument, the
8
+ typical voice. Each choice is reasonable and the sum reads like nobody in particular wrote it.
9
+ The writing profile names the gaps a piece of writing has, so each one is filled on purpose, by
10
+ someone you can name, and checked before a draft reaches you.
11
+
12
+ A profile adds rules for one kind of work without adding a test. Every writing finding reports
13
+ under one of the nine tests in [SPEC.md](SPEC.md), with an id that starts `writing-`, and the
14
+ score is still out of nine. Everything in the core format (decisions, requirements, rejects,
15
+ examples, resume, feedback, improvement) still applies and is still linted.
16
+
17
+ ## Opting in
18
+
19
+ Add `profile: writing` at the top level of the frontmatter, and a `writing:` map holding the
20
+ blocks. `hyperspec lint` then prints one more line under the score:
21
+
22
+ ```
23
+ essay.hyperspec.md: pass (9/9)
24
+ writing: 9/9 blocks complete
25
+ ```
26
+
27
+ A block counts as complete when it is present with no failing finding. `characters` also counts
28
+ as complete when the piece is not fiction, since nothing requires it. A block deferred to a
29
+ decision (see [Deferring a block](#deferring-a-block)) does not count as complete, because
30
+ nothing has been written in it yet. `--json` carries the same count on each file as
31
+ `"profile": { "name": "writing", "complete": 9, "total": 9 }`.
32
+
33
+ A `profile:` this linter does not know is a warning under test 7, naming the profile and saying
34
+ its rules were not checked.
35
+
36
+ ## The ten components
37
+
38
+ These are the parts you cannot remove without the writing sliding back to the middle. Nine are
39
+ blocks under `writing:`. The tenth, progress, is deliberately never written down.
40
+
41
+ ### 1. Materials: what goes in, and how it is marked
42
+
43
+ Brain dumps, transcripts, interview answers, notes, prior pieces, research. Each material
44
+ records who produced it, when it was captured, how, and how far it can be trusted: `raw` for
45
+ thinking out loud, `considered` for something someone has reviewed, `verified` for something
46
+ checked against its source. A brain dump is the most valuable input and the least structured, so
47
+ marking it is the step that turns thinking into something an agent can cite. Each material is
48
+ split into segments and each segment gets a label saying what it may be used as (see
49
+ [Materials labels](#materials-labels)).
50
+
51
+ ### 2. Writer DNA: who is writing, and how they sound, for this purpose
52
+
53
+ A writer does not have one voice. The same person writes differently for a theology journal and
54
+ a landing page, so their DNA is scoped by **form, audience and purpose**, and the scope is the
55
+ whole design of this block.
56
+
57
+ - **Rules** are the one layer that applies everywhere: what the writer never does and always
58
+ does. They live in your style rules file, and every piece in every scope points at it.
59
+ - **Goldens** are real passages the writer has marked as right, each filed under its scope. A
60
+ golden feeds only work that shares that scope, so a sermon golden can never leak into a sales
61
+ email.
62
+ - **Every golden carries a note on why it is golden.** A golden without its reason teaches the
63
+ surface; the reason teaches the move.
64
+
65
+ DNA is proven by a blind lineup within its scope: a judge sees a generated passage beside real
66
+ goldens of the same kind and tries to pick it out. Every writer has their own DNA, and nobody's
67
+ scope feeds anybody else's.
68
+
69
+ ### 3. Persona: who the piece speaks as
70
+
71
+ The identity the piece speaks as (the writer as themselves, a role, or a character), its stance
72
+ toward the reader, what it may assert, and what it will not say. Research on persona prompting
73
+ finds that a persona shapes voice and alignment and can cost accuracy
74
+ ([PRISM](https://arxiv.org/html/2603.18507v1)), so here the persona governs voice and stance only.
75
+ **Facts come from marked sources, never from the persona.** That separation is a field,
76
+ `facts_from: sources`, and the linter refuses any other value.
77
+
78
+ The persona is recorded in the spec, where it belongs. The piece itself says nothing to the
79
+ reader about who is writing.
80
+
81
+ ### 4. Audience: who is reading
82
+
83
+ A named person or a specified archetype: where they are on their path right now, which words
84
+ they already have, what they believe now, what they want, where they will read it (a phone in
85
+ ninety seconds, aloud, in print), and whether the reader is a person or another agent. Checked
86
+ by a term station (every word outside their vocabulary is defined on first use) and by a
87
+ simulated reader, a model playing that exact reader, which reports where it got lost and where
88
+ it would have stopped.
89
+
90
+ ### 5. Goal: what the piece changes
91
+
92
+ The one step down the funnel: where this reader is now, where the piece moves them, and what
93
+ they do next if it worked. Then the change that step needs in them (a belief, an action or a
94
+ feeling), and five to ten requirements that would prove it happened, each written to be failable.
95
+ This is the block a grader works against, and the simulated reader is asked the one question that
96
+ matters: would you take the next step now?
97
+
98
+ ### 6. Form: what kind of thing it is
99
+
100
+ Essay, chapter, wiki article, email, text, talk, letter, story. A form supplies its required
101
+ parts, its length envelope, and any extra checks it needs: a chapter checks continuity with the
102
+ chapters around it, a wiki article checks its links, a text message checks bubble length.
103
+
104
+ ### 7. Spine: what it argues
105
+
106
+ The kind of argument (thesis, testimony, primer, letter, story; the set is open) and the claim
107
+ chain: the three to seven claims the piece has to land, in order, each pointing at the materials
108
+ that support it.
109
+
110
+ ### 8. Sources and claims: what lets another agent pick it up
111
+
112
+ A claims ledger beside the piece: every factual claim in the draft points at a source and the
113
+ span in it, and every quote is matched word for word against its source. A writer's own brain
114
+ dump counts as a source for their opinions and their stories, never for an outside fact. By
115
+ default a claim with no source fails the check rather than passing with a warning.
116
+
117
+ ### 9. Characters: everyone who speaks inside the work
118
+
119
+ The persona is who the piece speaks as. A character is someone who speaks inside it, and the
120
+ same rules apply one level down. Required when `fiction: true`. Each character carries:
121
+
122
+ - **Speech DNA**: the words they use, the ones they never would, and their rhythm.
123
+ - **Wants, fears, and the thing they hide**, because dialogue is someone trying to get something
124
+ while hiding something.
125
+ - **What they know, and when.** A knowledge timeline by chapter or scene, so a character never
126
+ says what they could not know yet. This is the most common way generated dialogue breaks a
127
+ story, and it is fully checkable.
128
+ - **How they speak to each person that matters**, since speech shifts by relationship.
129
+ - **Arc state**: where they are in their change at this point in the work.
130
+ - **Golden lines and rejected lines**: real lines that sound exactly like them, and lines that
131
+ sound close but wrong.
132
+
133
+ Checked by a blind attribution test (a judge sees a line with the speaker hidden and has to name
134
+ who said it), a knowledge-leak check against the timeline, and a consistency check against the
135
+ golden and rejected lines. If your characters already live as entities in a world database, the
136
+ optional `entity` field points at that record, so one record can say how a character looks and
137
+ how they speak.
138
+
139
+ ### 10. Progress: where it is
140
+
141
+ Derived from disk, never stored. Which blocks are filled, which drafts exist, which checks
142
+ passed, what the last review said, and the next action are all things a second agent can read
143
+ off the folder itself. A saved progress field goes stale the first time a session dies mid-arc,
144
+ and from then on it lies to every agent that trusts it. So the linter refuses one: a
145
+ `writing.progress` key fails test 7, and `resume.next_action` stays the only thing the spec
146
+ says about what happens next.
147
+
148
+ ## The schema
149
+
150
+ Every field below is required unless its comment says otherwise, and a required list needs at
151
+ least one entry. A path resolves relative to the spec file, the same way `examples` does, and a
152
+ path the linter checks must name a file that exists.
153
+
154
+ ```yaml
155
+ profile: writing
156
+ fiction: false # optional, true or false; absent means false. true requires writing.characters
157
+ writing:
158
+ materials:
159
+ items: # at least one
160
+ - id: voice-memo # unique across items
161
+ path: materials/voice-memo.md
162
+ produced_by: example-author
163
+ captured: "2026-09-12"
164
+ how: voice memo, transcribed
165
+ trust: raw # raw | considered | verified
166
+ check:
167
+ station: every segment of every material carries a label from the closed set
168
+ source: capture step
169
+ author: agent:claude
170
+ dna:
171
+ writer: example-author
172
+ scope:
173
+ form: essay
174
+ audience: new managers
175
+ purpose: teach
176
+ rules: style-rules.md # your style rules file, the always-on layer
177
+ goldens: # at least one
178
+ - path: goldens/opening.md
179
+ why: one plain claim, then a second sentence that turns it into something to do
180
+ check:
181
+ rubric: blind lineup within this scope
182
+ source: goldens marked on the review page
183
+ author: example-author
184
+ persona:
185
+ identity: self # self | role:<name> | character:<id>
186
+ stance: mentor # peer | mentor | witness | guide; anything else warns
187
+ may_assert:
188
+ - what the author did in their own first one-on-ones
189
+ will_not_say:
190
+ - the name of anyone on the author's team
191
+ facts_from: sources # must be exactly "sources"
192
+ check:
193
+ rubric: persona-consistency judge
194
+ source: persona interview
195
+ author: example-author
196
+ audience:
197
+ who: someone in their first three months of managing
198
+ funnel_now: has a first one-on-one on the calendar this week
199
+ knows:
200
+ - one-on-one
201
+ - report
202
+ believes_now: a one-on-one is where a manager catches up on the work
203
+ wants: a plan for the first meeting
204
+ reads_on: a phone, in the ten minutes before the meeting
205
+ reader: person # person | agent
206
+ check:
207
+ station: term check against knows
208
+ rubric: simulated reader reports where it got lost
209
+ source: audience interview
210
+ author: example-author
211
+ goal:
212
+ from: plans to run the meeting from their own list
213
+ to: hands the meeting to the report
214
+ next_if_worked: copies the three questions into the invite
215
+ change:
216
+ kind: action # belief | action | feeling
217
+ text: the reader asks the three questions and waits
218
+ conditions: # 5 to 10 distinct ids of top-level requirements, each once
219
+ - r1
220
+ - r2
221
+ - r3
222
+ - r4
223
+ - r5
224
+ check:
225
+ rubric: grade the draft against every condition
226
+ source: goal interview
227
+ author: example-author
228
+ form:
229
+ name: essay # open set
230
+ length: # whole numbers, at least 1, min no more than max
231
+ min: 700
232
+ max: 1100
233
+ unit: words
234
+ required_parts:
235
+ - an opening that states the claim
236
+ - the three questions
237
+ - a close
238
+ stations: # may be empty
239
+ - the three questions render as a numbered list
240
+ check:
241
+ station: structure and length check
242
+ source: form decision
243
+ author: example-author
244
+ spine:
245
+ kind: primer # open set
246
+ claims: # 3 to 7, in order, each with its own id
247
+ - id: c1
248
+ text: the first one-on-one is the one meeting the report should set the agenda for
249
+ materials: # ids from materials.items; voice-memo#segment is accepted
250
+ - voice-memo
251
+ - id: c2
252
+ text: status belongs in the tracker
253
+ materials:
254
+ - voice-memo
255
+ - id: c3
256
+ text: three questions are enough to hand the meeting over
257
+ materials:
258
+ - voice-memo
259
+ check:
260
+ rubric: each claim lands, in order, and nothing is argued outside the chain
261
+ source: spine interview
262
+ author: example-author
263
+ sources:
264
+ ledger: claims.jsonl # need not exist before drafting
265
+ unsourced_claim: fail # fail | warn; warn is reported as a warning
266
+ check:
267
+ station: every factual claim points at a source span
268
+ source: sourcing pass
269
+ author: agent:claude
270
+ characters: # required when fiction: true; ids unique
271
+ - id: ines
272
+ entity: world/ines.json # optional; if given, the file must exist
273
+ speech:
274
+ uses:
275
+ - instructions in the imperative
276
+ never:
277
+ - an apology in words
278
+ rhythm: short, flat sentences # optional
279
+ wants: to leave the bakery in hands she trusts
280
+ fears: that he will stay in a trade with no bakery to do it in
281
+ hides: that she sold the bakery two weeks ago
282
+ knowledge: # at least one; each entry needs by and knows
283
+ - by: scene-1
284
+ knows: the sale closes on Friday
285
+ relationships: # optional
286
+ - to: theo
287
+ how: gives him instructions where another person would give praise
288
+ arc_state: has let go of the bakery, and has not yet let go of him
289
+ golden_lines: # at least one
290
+ - Flour first. Then you can talk.
291
+ rejected_lines: # at least one, and none of them also golden
292
+ - I'm so sorry I didn't tell you sooner.
293
+ check:
294
+ rubric: blind attribution test, knowledge-leak check, consistency against golden and rejected lines
295
+ source: notes.md
296
+ author: example-author
297
+ ```
298
+
299
+ Every block, and every character, carries `check` (with `station:` for a deterministic check or
300
+ `rubric:` for what a grader applies, the same shape a requirement's check has), `source` and
301
+ `author`. `characters` is a list, so there each entry carries its own.
302
+
303
+ Write every map in block style, one key per line, as above. The YAML reader hyperspec uses reads
304
+ a flow list such as `[r1, r2]`, but it reads an inline map such as `{ station: ... }` as a plain
305
+ string, and a flow list followed by a comment on the same line as a string too.
306
+
307
+ A placeholder counts as missing, here and everywhere else in a hyperspec, so a scaffolded field
308
+ cannot pass a presence check. A placeholder is a whole value, trimmed and in any case, of `todo`,
309
+ `tbd`, `fixme`, `xxx`, `placeholder`, `<placeholder>`, `n/a`, a run of dashes, a run of question
310
+ marks, or an ellipsis, optionally followed by a trailing `.`, `:` or `!`. Real text that starts
311
+ with one of those, such as `TODO: write the opening`, counts as present, and so does `none`.
312
+
313
+ ## The test mapping
314
+
315
+ Each row lists what the writing profile adds to that test. The core conditions in
316
+ [SPEC.md](SPEC.md#the-test-to-field-map) still apply alongside them.
317
+
318
+ | Test | A writing spec fails it when |
319
+ |---|---|
320
+ | 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 `stance` outside the four is a warning, and so is `unsourced_claim: warn` |
321
+ | 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 |
322
+ | 3 every requirement names its check | a block or a character has no `check` with a `station` or a `rubric` |
323
+ | 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` |
324
+ | 5 negative space is specified | `persona.will_not_say` is empty; `persona.facts_from` is anything other than `sources` |
325
+ | 6 examples outrank adjectives | a golden has no `why`; a material, `dna.rules`, golden or character `entity` path does not exist or is not a file; a character has no golden lines or no rejected lines, or has the same line in both (compared trimmed and case-folded) |
326
+ | 7 a stranger can resume it | `writing.progress` exists. An unknown `profile:` is a warning |
327
+ | 8 its adopters can push back on it | nothing further; the core rule applies |
328
+ | 9 it improves itself | nothing further; the core rule applies |
329
+
330
+ ## Closed sets
331
+
332
+ | Field | Allowed values |
333
+ |---|---|
334
+ | `materials.items[].trust` | `raw`, `considered`, `verified` |
335
+ | `persona.identity` | `self`, `role:<name>`, `character:<id>` |
336
+ | `persona.stance` | `peer`, `mentor`, `witness`, `guide`; any other value warns |
337
+ | `persona.facts_from` | `sources` |
338
+ | `audience.reader` | `person`, `agent` |
339
+ | `goal.change.kind` | `belief`, `action`, `feeling` |
340
+ | `sources.unsourced_claim` | `fail`, `warn` |
341
+ | `fiction` | `true`, `false`; absent means `false` |
342
+
343
+ `form.name` and `spine.kind` are open: name the form and the kind of argument in your own words.
344
+
345
+ ## Materials labels
346
+
347
+ Before a material is used, it is split into segments, and each segment gets one of seven labels.
348
+ The label decides what the segment may become in the draft.
349
+
350
+ | Label | Means | May be used as |
351
+ |---|---|---|
352
+ | `claim` | a statement of fact about the world | only with a source, or as the author's own claim said as such |
353
+ | `story` | something that happened, told by someone who was there | testimony, with the teller named |
354
+ | `quote` | words someone said, verbatim | quoted exactly, never paraphrased inside quotation marks |
355
+ | `stance` | an opinion or conviction | the author's position |
356
+ | `question` | something open | a prompt for the interview, never an assertion |
357
+ | `aside` | true but off the thread | held back unless the spine needs it |
358
+ | `private` | not for this audience | never used; kept for context |
359
+
360
+ The linter defines the set once, as `MATERIAL_LABELS` in `src/writing.mjs`. This release checks
361
+ the materials list itself; it does not yet read segment files or check their labels. A spine
362
+ claim may point at a segment as `m1#segment`, and today only the material id before the `#` is
363
+ checked.
364
+
365
+ ## Deferring a block
366
+
367
+ A block can be deferred, never silently missing. A required block that is absent fails test 1
368
+ unless a decision with the id `writing-<block>` stands in for it:
369
+
370
+ ```yaml
371
+ decisions:
372
+ - id: writing-audience
373
+ state: open
374
+ question: who reads this, and what do they already believe?
375
+ source: kickoff call
376
+ author: agent:claude
377
+ chosen_by: agent
378
+ ```
379
+
380
+ - **`open`** defers the block to a question only a person can answer. The spec is blocked on it,
381
+ exactly like any open decision: `lint` exits 3 once every test passes.
382
+ - **`delegated` with a `rule`** defers the block to a standing rule the agent follows. The spec
383
+ can pass. A delegated decision with no `rule` defers nothing, so the missing block still fails.
384
+
385
+ Either way the deferred block does not count toward `writing: k/9 blocks complete`.
386
+
387
+ ## Starting a writing spec
388
+
389
+ ```bash
390
+ npx @supersuit/hyperspec init essay.hyperspec.md --profile writing --title "Your title" --form essay
391
+ npx @supersuit/hyperspec init story.hyperspec.md --profile writing --form "short story" --fiction
392
+ ```
393
+
394
+ The skeleton shows every required block in schema order with every field present as a `TODO`
395
+ placeholder. `dna`, `persona`, `audience` and `goal` also carry an open decision whose question
396
+ says what you have to answer before the placeholder means anything. `--form` sets both `kind:`
397
+ and `writing.form.name`, and defaults to `essay`. `--fiction` sets `fiction: true` and adds one
398
+ character with the same treatment. The skeleton never passes: it lints `fail`, with
399
+ `writing: 1/9 blocks complete` (or `0/9` with `--fiction`), until the placeholders and the open
400
+ decisions are replaced with real content.
401
+
402
+ `init` refuses, with exit 2 and a plain message, anything it would otherwise have to ignore:
403
+ `--profile` with no value or one this linter does not know, `--fiction` or `--form` without
404
+ `--profile writing`, `--kind` with it (the form sets the kind), and a file in a folder that does
405
+ not exist.
406
+
407
+ ## Worked examples
408
+
409
+ Two complete specs ship in [`examples/writing/`](examples/writing/), each with every file it
410
+ names:
411
+
412
+ - `essay.hyperspec.md`: an essay for new managers on running a first one-on-one. Three materials
413
+ at three trust levels, scoped DNA with two annotated goldens, a four-claim spine.
414
+ - `story.hyperspec.md`: a short story, `fiction: true`, narrated by one of its two characters.
415
+ Each character has speech rules, a knowledge timeline by scene, and golden and rejected lines
416
+ in a voice you can tell apart from the other's.
417
+
418
+ Both lint `pass (9/9)` with `writing: 9/9 blocks complete` and no findings. A test runs them on
419
+ every release, so they cannot drift from the linter.
420
+
421
+ ## What later versions add
422
+
423
+ This release is the schema and its lint. Later versions build on it in order: marking materials
424
+ (a brain dump or transcript in, labeled segments out, with the labels above enforced), scoped
425
+ DNA with annotated goldens filed by form, audience and purpose, and the stations themselves,
426
+ running the checks each block names and grading drafts against the goal.