@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.
- package/CHANGELOG.md +127 -0
- package/README.md +46 -1
- package/SPEC.md +5 -3
- package/WRITING.md +475 -40
- package/bin/hyperspec.mjs +157 -3
- package/examples/writing/dna/essay-new-managers-teach/features.json +56 -0
- package/examples/writing/dna/essay-new-managers-teach/goldens/README.md +14 -0
- package/examples/writing/dna/essay-new-managers-teach/goldens/close.md +9 -0
- package/examples/writing/dna/essay-new-managers-teach/goldens/opening.md +9 -0
- package/examples/writing/dna/essay-new-managers-teach/goldens/status.md +10 -0
- package/examples/writing/dna/essay-new-managers-teach/scope.md +11 -0
- package/examples/writing/essay/materials/interview-notes.md.segments.jsonl +10 -0
- package/examples/writing/essay/materials/team-survey.md.segments.jsonl +6 -0
- package/examples/writing/essay/materials/voice-memo.md.segments.jsonl +7 -0
- package/examples/writing/essay.hyperspec.md +24 -13
- package/examples/writing/story/materials/bakery-visit.md +1 -0
- package/examples/writing/story/materials/bakery-visit.md.segments.jsonl +13 -0
- package/examples/writing/story/materials/notes.md +2 -0
- package/examples/writing/story/materials/notes.md.segments.jsonl +7 -0
- package/examples/writing/story/materials/scene-list.md.segments.jsonl +14 -0
- package/examples/writing/story.hyperspec.md +8 -5
- package/package.json +2 -1
- package/src/blobs.mjs +1 -1
- package/src/compare.mjs +6 -6
- package/src/dna.mjs +471 -0
- package/src/fsutil.mjs +1 -1
- package/src/labels.mjs +6 -0
- package/src/reproduce.mjs +5 -5
- package/src/segments.mjs +407 -0
- package/src/writing-exports.mjs +11 -0
- package/src/writing-fields.mjs +279 -6
- package/src/writing-template.mjs +16 -1
- package/src/writing.mjs +4 -4
- package/examples/writing/essay/goldens/close.md +0 -2
- 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.
|
|
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.
|
|
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,
|
|
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
|
|