@supersuit/hyperspec 0.3.0 → 0.5.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +46 -1
  3. package/SPEC.md +5 -3
  4. package/WRITING.md +475 -40
  5. package/bin/hyperspec.mjs +157 -3
  6. package/examples/writing/dna/essay-new-managers-teach/features.json +56 -0
  7. package/examples/writing/dna/essay-new-managers-teach/goldens/README.md +14 -0
  8. package/examples/writing/dna/essay-new-managers-teach/goldens/close.md +9 -0
  9. package/examples/writing/dna/essay-new-managers-teach/goldens/opening.md +9 -0
  10. package/examples/writing/dna/essay-new-managers-teach/goldens/status.md +10 -0
  11. package/examples/writing/dna/essay-new-managers-teach/scope.md +11 -0
  12. package/examples/writing/essay/materials/interview-notes.md.segments.jsonl +10 -0
  13. package/examples/writing/essay/materials/team-survey.md.segments.jsonl +6 -0
  14. package/examples/writing/essay/materials/voice-memo.md.segments.jsonl +7 -0
  15. package/examples/writing/essay.hyperspec.md +24 -13
  16. package/examples/writing/story/materials/bakery-visit.md +1 -0
  17. package/examples/writing/story/materials/bakery-visit.md.segments.jsonl +13 -0
  18. package/examples/writing/story/materials/notes.md +2 -0
  19. package/examples/writing/story/materials/notes.md.segments.jsonl +7 -0
  20. package/examples/writing/story/materials/scene-list.md.segments.jsonl +14 -0
  21. package/examples/writing/story.hyperspec.md +8 -5
  22. package/package.json +2 -1
  23. package/src/blobs.mjs +1 -1
  24. package/src/compare.mjs +6 -6
  25. package/src/dna.mjs +471 -0
  26. package/src/fsutil.mjs +1 -1
  27. package/src/labels.mjs +6 -0
  28. package/src/reproduce.mjs +5 -5
  29. package/src/segments.mjs +407 -0
  30. package/src/writing-exports.mjs +11 -0
  31. package/src/writing-fields.mjs +279 -6
  32. package/src/writing-template.mjs +16 -1
  33. package/src/writing.mjs +4 -4
  34. package/examples/writing/essay/goldens/close.md +0 -2
  35. package/examples/writing/essay/goldens/opening.md +0 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,132 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 (2026-09-29)
4
+
5
+ Scoped writer DNA. A writer does not have one voice: the same person writes differently for a
6
+ theology journal and a landing page. So a writer's voice is now kept per scope, a form, an
7
+ audience and a purpose, as a folder of goldens: real passages a person approved, each with a note
8
+ on the move it teaches and where it came from. A golden feeds only work that shares its scope, so
9
+ a passage that is right for one kind of writing never teaches its moves to another. hyperspec
10
+ measures each scope's style from its goldens (sentence and paragraph length, punctuation,
11
+ pronouns, signature words), counts and never judges, and still calls no model.
12
+
13
+ **No behavior change for existing specs.** Scoped DNA is opt-in through a new optional field,
14
+ `writing.dna.scope_dir`. A spec without it passes and fails exactly as it did in 0.4.0, and no
15
+ finding id changed: every id below is new.
16
+
17
+ - `hyperspec dna init <scope-dir> --writer W --form F --audience A --purpose P` writes a scope
18
+ folder: `scope.md` (writer, form, audience, purpose, optional notes) and a `goldens/` folder
19
+ holding a README on the golden file shape. It refuses to overwrite an existing `scope.md`, never
20
+ replaces a `goldens/README.md` that is already there, and
21
+ exits 2 with a plain message on a missing flag, a flag whose value is a placeholder, or a
22
+ parent folder that does not exist. Paths print as you gave them.
23
+ - A golden is one `.md` file directly in `goldens/`, other than `README.md`: frontmatter `why`
24
+ (the move it teaches), `approved_by` (a person; an approver starting `agent:` is refused,
25
+ because golden means a human approved it) and `source` are required, `approved_on` is optional,
26
+ and the body is the passage, verbatim. A `goldens/` folder that resolves outside its scope, such
27
+ as a symlink to another scope's goldens, is refused under test 5, naming where it leads.
28
+ - `hyperspec dna measure <scope-dir> [--json]` checks every golden and writes
29
+ `<scope-dir>/features.json`: the scope, each golden's path and SHA-256, and the features. If
30
+ the scope or any golden fails a check it writes nothing and exits 1, so a hollow or borrowed
31
+ golden is never measured in. The same goldens always produce the same bytes.
32
+ - The features: word count; sentence length in words (mean, median, 90th percentile); paragraph
33
+ length in sentences and in words; per-1000-word rates of commas, semicolons, colons, em dashes,
34
+ en dashes, exclamation marks, question marks, parentheses and quotation marks; contraction,
35
+ first-person singular, first-person plural and second-person rates; mean word length; and up to
36
+ 15 signature words. Sentences and paragraphs are split the same way `segments init` splits
37
+ them.
38
+ - With `writing.dna.scope_dir`, `lint` checks that `scope.md` matches the spec's writer, form,
39
+ audience and purpose (test 1); that every golden the spec lists is one of the scope's goldens,
40
+ after following any symlink, and never a passage in a subfolder, in `README.md` or in another
41
+ kind of file (test 5); that every golden in the folder has its `why` (test 6), a person's
42
+ approval and a source (test 4) and a passage (test 1); and that `features.json` is exactly what
43
+ `dna measure` would write now (test 6). The stale finding names what differs: goldens added,
44
+ removed or changed, a changed `scope.md` field, an unknown format version, or a number edited by
45
+ hand. A `scope_dir` that is present but a placeholder fails test 1.
46
+ - New finding ids, all starting `writing-dna-`: `scope-dir`, `scope-missing`,
47
+ `scope-file-<field>` (a scope's `scope.md` lacks writer, form, audience or purpose),
48
+ `scope-mismatch-<field>`, `goldens-missing`, `goldens-empty`, `goldens-outside`,
49
+ `golden-unreadable`, `golden-frontmatter`, `golden-empty`, `golden-approved-by`,
50
+ `golden-approved-by-agent`, `golden-source`, `golden-leak`, `golden-why`, `features-missing` and
51
+ `features-stale`. Every existing id is unchanged; in particular a spec whose own
52
+ `writing.dna.scope` lacks a field still reports `writing-dna-scope-form` (and `-audience`,
53
+ `-purpose`) as in 0.4.0. Every message names the scope folder as the spec wrote it and the
54
+ golden by its path inside the folder, never a folder on your machine. WRITING.md lists every one
55
+ with its test.
56
+ - `hyperspec init --profile writing` shows `scope_dir: TODO` in the `dna` block, which fails until
57
+ it names a scope folder or is deleted.
58
+ - Two new exports from `@supersuit/hyperspec/writing`: `readScope`, which reads a scope folder
59
+ and returns `{ scope, goldens, findings }` without throwing, and `measureFeatures`, which takes
60
+ an array of passages and returns the features `dna measure` writes.
61
+ - The essay example takes its voice from a scope folder,
62
+ `examples/writing/dna/essay-new-managers-teach/`, with three goldens and a measured
63
+ `features.json`. Its two goldens moved there from `examples/writing/essay/goldens/`, and a third
64
+ was added. The story example keeps its goldens in the spec with no scope folder, and still
65
+ passes.
66
+ - WRITING.md gains a Scoped DNA section: why a writer's DNA is scoped, the folder shape, the
67
+ golden file, both commands with their output, what each feature measures and what it is for,
68
+ staleness, naming a scope in a spec, every finding with its test, and the exports. README and
69
+ SPEC.md point at it.
70
+
71
+ ## 0.4.0 (2026-09-29)
72
+
73
+ Marking materials. Before a writing spec can pass, every material it draws on (a brain dump, a
74
+ transcript, a set of interview notes) is split into segments, and each segment is labeled with
75
+ what a draft may use it as: a claim with its source, the author's own claim, a story with its
76
+ teller, a quote with its speaker, a stance, an open question, an aside, or something private.
77
+ The spine then cites segments rather than whole files, so every claim points at the exact words
78
+ behind it. hyperspec splits and checks; an agent or a person labels. It still calls no model.
79
+
80
+ **Behavior change for 0.3 writing specs:** a writing spec's materials must now be marked. A
81
+ material item with no `segments:` field fails test 1, so a writing spec that passed 0.3.0 fails
82
+ until each of its materials has a segments file, written with `hyperspec segments init` and
83
+ labeled. Specs with no profile are unaffected.
84
+
85
+ - `hyperspec segments init <material> --id <mid> [--out <file>] [--by paragraph|sentence]`
86
+ writes `<material>.segments.jsonl`: a header naming the material, its path and the SHA-256 of
87
+ its bytes, then one line per segment with character offsets, the verbatim text, and the label
88
+ `unlabeled`. Paragraph mode is the default. It refuses to overwrite a file, and exits 2 with a
89
+ plain message on a missing material, a material with nothing in it, an `--out` folder that
90
+ does not exist, or an unknown `--by`.
91
+ - In sentence mode, a new line that opens on a list marker (`-`, `*`, `+`, or a number followed
92
+ by `.` or `)`, then a space) starts a new segment, and a numbered item's own `1.` is not read
93
+ as a sentence ending. A bullet that is entirely a quotation ending in `."` used to run into the
94
+ next bullet; it now stands on its own. Paragraph mode is unchanged.
95
+ - Seven labels, a closed set: `claim` (needs `source`, or `own: true`), `story` (needs
96
+ `teller`), `quote` (needs `speaker`), `stance`, `question`, `aside` and `private`. `unlabeled`
97
+ is never accepted. A placeholder word (`TODO`, `n/a`, `tbd`, `...`, `???` and the rest) counts
98
+ as missing in these fields, in the header, and in segment ids, as it does everywhere else in
99
+ the linter.
100
+ - What `lint` checks on each segments file, under test 1: the file exists and parses, its header
101
+ names the right material, every label is from the set, ids are unique, and segments never
102
+ overlap and cover every character that is not whitespace (the finding quotes the first
103
+ uncovered text and gives its offset), and the material has some text to mark. Under test 4: every segment's text
104
+ matches the material word for word, each label carries the field it needs, and the material
105
+ has not changed since it was marked (its SHA-256 still matches). A changed material fails as
106
+ stale until it is marked again.
107
+ - A spine claim may cite `material#segment`. The segment must exist (test 4), and citing a
108
+ `private` or `question` segment fails test 5. A bare material id still cites the whole
109
+ material; `material#` with nothing after the `#` fails as an unknown segment.
110
+ - Every marking finding id starts `writing-materials-` or `writing-spine-`, and every message
111
+ names the material, and the segment where there is one. Paths in messages read as the spec
112
+ wrote them, never resolved to a folder on your machine, so `--json` output is the same
113
+ everywhere. WRITING.md lists every finding with its test.
114
+ - A new export, `@supersuit/hyperspec/writing`, gives your own tools `MATERIAL_LABELS` and
115
+ `readSegments`, the same parse-and-check lint runs. `readSegments` returns
116
+ `{ header, segments, findings }` and never throws; `displayPath` and `materialDisplayPath` set
117
+ how the files are named in its messages.
118
+ - `hyperspec init --profile writing` names a segments file for its placeholder material, and the
119
+ materials check reads "every segment of every material carries a label from the closed set,
120
+ matches its source verbatim, and the markings are current".
121
+ - Both writing examples ship with every material marked. Between them they use all seven labels,
122
+ and every spine claim cites segments.
123
+ - WRITING.md gains a Marking materials section: the file format with a worked sample, the labels
124
+ and what each needs, coverage, staleness (including a line-ending conversion, which changes the
125
+ hash), citing segments, every finding with its test, and the import. README and SPEC.md point
126
+ at it.
127
+ - The repository's `.gitattributes` keeps example materials and test fixtures LF on every
128
+ checkout, so the hashes their segments files pin still match on a Windows clone.
129
+
3
130
  ## 0.3.0 (2026-09-29)
4
131
 
5
132
  The writing profile. A piece of writing can now carry a hyperspec that names everything an
package/README.md CHANGED
@@ -25,13 +25,16 @@ improvement ledger. Every test is defined in [SPEC.md](SPEC.md).
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
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. |
29
+ | `hyperspec dna init <scope-dir> --writer W --form F --audience A --purpose P` | Start a writer-DNA scope folder. Refuses to overwrite an existing `scope.md`. |
30
+ | `hyperspec dna measure <scope-dir>` | Check every golden in a scope and write its measured features. |
28
31
  | `hyperspec recipe check <output-or-recipe>` | Check that a recipe records everything the standard asks for. |
29
32
  | `hyperspec recipe approve <recipe> --by <slug>` | Record who approved the output. |
30
33
  | `hyperspec reproduce <recipe> [--restore]` | Re-check every hash the recipe recorded. Never runs a model. |
31
34
  | `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. |
32
35
  | `hyperspec compare <child-recipe> --doctor cmd` | Grade a child and its parent through one doctor against one spec. |
33
36
 
34
- Every command except `init` takes `--json`. `hyperspec --help` prints every flag.
37
+ Every command except `init`, `segments init` and `dna init` takes `--json`. `hyperspec --help` prints every flag.
35
38
 
36
39
  ## Exit codes
37
40
 
@@ -131,6 +134,48 @@ each rule reports under are in [WRITING.md](WRITING.md). Two complete specs that
131
134
  nothing to warn ship in `examples/writing/`: an essay for new managers, and a short story with
132
135
  two characters whose voices a judge can tell apart.
133
136
 
137
+ ### Marking materials
138
+
139
+ Every material a writing spec draws on is marked before the spec can pass: split into segments,
140
+ and each segment labeled with what it may be used as (claim, story, quote, stance, question,
141
+ aside, private). hyperspec does the splitting and the checking; an agent or a person does the
142
+ labeling.
143
+
144
+ ```bash
145
+ npx @supersuit/hyperspec segments init materials/voice-memo.md --id voice-memo
146
+ npx @supersuit/hyperspec lint essay.hyperspec.md
147
+ ```
148
+
149
+ The first writes `materials/voice-memo.md.segments.jsonl`, every segment `unlabeled`. Name that
150
+ file as `segments:` on the material item, label every segment, and `lint` checks that each label
151
+ is from the set and carries what it needs, that each segment matches the material word for word,
152
+ that the material has not changed since, and that no spine claim cites a private or question
153
+ segment. The file format, the labels, and every finding are in
154
+ [WRITING.md](WRITING.md#marking-materials). A tool of your own can run the same check with
155
+ `import { readSegments } from "@supersuit/hyperspec/writing"`.
156
+
157
+ ### Scoped DNA
158
+
159
+ A writer sounds different in a theology essay and on a landing page, so a writer's voice is kept
160
+ per scope (a form, an audience and a purpose), and each scope is a folder of goldens. A golden is a real
161
+ passage a person approved, with a note on the move it teaches and where it came from, and it
162
+ feeds only work that shares its scope.
163
+
164
+ ```bash
165
+ mkdir -p dna
166
+ npx @supersuit/hyperspec dna init dna/essay-new-managers-teach --writer example-author --form essay --audience "new managers" --purpose teach
167
+ npx @supersuit/hyperspec dna measure dna/essay-new-managers-teach
168
+ ```
169
+
170
+ `dna init` writes `scope.md` and a `goldens/` folder holding only a README. Add one file per
171
+ golden, then `dna measure` checks each golden and writes `features.json`: sentence and paragraph length, punctuation,
172
+ pronouns and signature words, measured and never judged. Name the folder in a spec as
173
+ `writing.dna.scope_dir` and `lint` checks that the scope matches the spec, that no golden comes
174
+ from another scope, and that the measurements are current. Without `scope_dir`, a spec lints as
175
+ it did in 0.4. The folder shape, every feature, and every finding are in
176
+ [WRITING.md](WRITING.md#scoped-dna); `readScope` and `measureFeatures` are exported from
177
+ `@supersuit/hyperspec/writing`.
178
+
134
179
  ## The format
135
180
 
136
181
  A hyperspec is a markdown file with a YAML frontmatter block: `decisions`, `requirements`,
package/SPEC.md CHANGED
@@ -97,7 +97,7 @@ examples:
97
97
  - path: examples/minimal.hyperspec.md
98
98
  why: the smallest spec that passes all nine tests
99
99
  resume:
100
- next_action: collect adopter issues on 0.3, the writing profile included, and cut 0.4 from them
100
+ next_action: collect adopter issues on 0.5, scoped DNA included, and cut 0.6 from them
101
101
  feedback:
102
102
  issues: https://github.com/SupersuitUp/hyperspec/issues
103
103
  fork: MIT; fork it for your own purposes and say so in your SPEC
@@ -109,7 +109,7 @@ improvement:
109
109
 
110
110
  A person writing for another person leaves most of the specification unsaid, because the other person fills the gaps from shared context. An agent has none of that context, so it fills every gap with the average, and the average is what reads as middling. Hyperspecification is writing down the gaps. It is a level of detail that would feel like overkill between two people and is exactly enough for an agent: every decision the agent would otherwise guess is either decided, delegated with the rule for deciding it, or marked open, so the work stops instead of guessing.
111
111
 
112
- **Version 0.3.0** (2026-09-29)
112
+ **Version 0.5.0** (2026-09-29)
113
113
 
114
114
  ## What makes a spec a hyperspec
115
115
 
@@ -220,6 +220,8 @@ Each row lists every condition under which `hyperspec lint` fails that test. A w
220
220
  | 8 its adopters can push back on it | `feedback.issues` or `feedback.fork` missing |
221
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 |
222
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). A writing spec that names a writer-DNA scope folder with `writing.dna.scope_dir` is also checked against it: the scope matches the spec (test 1), every golden has a person's approval and a source (test 4), no golden comes from outside the scope (test 5), and every golden has its why and the scope's measured features are current (test 6). `scope_dir` is optional, and without it nothing changes.
224
+
223
225
  ## Exit codes
224
226
 
225
227
  `hyperspec lint` reports the worst result across every file it is given:
@@ -243,7 +245,7 @@ Silence is not a verdict. A run that learned nothing has to say so and why, and
243
245
 
244
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.
245
247
 
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).
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`, marking materials with `hyperspec segments init`, and scoped writer DNA with `hyperspec dna init` and `hyperspec dna measure` are in [WRITING.md](WRITING.md).
247
249
 
248
250
  ## Recipes
249
251