@supersuit/hyperspec 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -0
- package/README.md +53 -3
- package/SPEC.md +24 -10
- package/WRITING.md +597 -0
- package/bin/hyperspec.mjs +94 -3
- package/examples/minimal.hyperspec.md +2 -2
- package/examples/writing/essay/goldens/close.md +2 -0
- package/examples/writing/essay/goldens/opening.md +2 -0
- package/examples/writing/essay/materials/interview-notes.md +12 -0
- package/examples/writing/essay/materials/interview-notes.md.segments.jsonl +10 -0
- package/examples/writing/essay/materials/team-survey.md +7 -0
- package/examples/writing/essay/materials/team-survey.md.segments.jsonl +6 -0
- package/examples/writing/essay/materials/voice-memo.md +18 -0
- package/examples/writing/essay/materials/voice-memo.md.segments.jsonl +7 -0
- package/examples/writing/essay/runs.jsonl +0 -0
- package/examples/writing/essay.hyperspec.md +220 -0
- package/examples/writing/story/goldens/dialogue.md +3 -0
- package/examples/writing/story/goldens/opening.md +3 -0
- package/examples/writing/story/materials/bakery-visit.md +9 -0
- package/examples/writing/story/materials/bakery-visit.md.segments.jsonl +13 -0
- package/examples/writing/story/materials/notes.md +16 -0
- package/examples/writing/story/materials/notes.md.segments.jsonl +7 -0
- package/examples/writing/story/materials/scene-list.md +7 -0
- package/examples/writing/story/materials/scene-list.md.segments.jsonl +14 -0
- package/examples/writing/story/runs.jsonl +0 -0
- package/examples/writing/story.hyperspec.md +288 -0
- package/examples/writing/style-rules.md +19 -0
- package/package.json +4 -2
- package/src/blobs.mjs +1 -1
- package/src/compare.mjs +6 -6
- package/src/fsutil.mjs +1 -1
- package/src/labels.mjs +6 -0
- package/src/placeholder.mjs +20 -0
- package/src/profiles.mjs +50 -0
- package/src/reproduce.mjs +5 -5
- package/src/rules.mjs +25 -13
- package/src/score.mjs +7 -1
- package/src/segments.mjs +407 -0
- package/src/template.mjs +4 -1
- package/src/writing-exports.mjs +6 -0
- package/src/writing-fields.mjs +418 -0
- package/src/writing-template.mjs +199 -0
- package/src/writing.mjs +181 -0
package/bin/hyperspec.mjs
CHANGED
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, statSync, writeFileSync, readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
3
4
|
import { loadSpec } from "../src/load.mjs";
|
|
4
5
|
import { lintSpec } from "../src/rules.mjs";
|
|
5
6
|
import { score, exitCode } from "../src/score.mjs";
|
|
6
7
|
import { template } from "../src/template.mjs";
|
|
8
|
+
import { writingTemplate } from "../src/writing-template.mjs";
|
|
9
|
+
import { PROFILES, knownProfile } from "../src/profiles.mjs";
|
|
7
10
|
import { readRecipe, checkRecipe } from "../src/recipe.mjs";
|
|
8
11
|
import { approve } from "../src/writer.mjs";
|
|
9
12
|
import { reproduce } from "../src/reproduce.mjs";
|
|
10
13
|
import { regenerate } from "../src/regenerate.mjs";
|
|
11
14
|
import { compare } from "../src/compare.mjs";
|
|
15
|
+
import { splitSegments } from "../src/segments.mjs";
|
|
16
|
+
import { sha256 } from "../src/hash.mjs";
|
|
12
17
|
|
|
13
18
|
const HELP = `hyperspec <command> [options]
|
|
14
19
|
|
|
15
20
|
lint <file...> [--json] score each hyperspec against the nine tests
|
|
16
21
|
exit 0 pass, 1 a test fails, 3 blocked on an open decision, 2 usage
|
|
17
22
|
init <file> [--title T] [--kind K] write a new hyperspec skeleton (refuses to overwrite)
|
|
23
|
+
init <file> --profile writing [--title T] [--form F] [--fiction]
|
|
24
|
+
write a writing-profile skeleton: every required block (materials,
|
|
25
|
+
dna, persona, audience, goal, form, spine, sources) shown in full
|
|
26
|
+
with placeholder values, dna/persona/audience/goal also carrying an
|
|
27
|
+
open decision naming the question only the operator can answer;
|
|
28
|
+
--fiction adds one character, same treatment; the skeleton never
|
|
29
|
+
passes until its placeholders and open decisions are replaced with
|
|
30
|
+
real content; exit 2 for a --profile with no value or one this
|
|
31
|
+
linter does not know, --fiction or --form without --profile
|
|
32
|
+
writing, --kind with it (use --form), or a folder that does not
|
|
33
|
+
exist
|
|
34
|
+
|
|
35
|
+
segments init <material> --id <mid> [--out <file>] [--by paragraph|sentence]
|
|
36
|
+
split a material into candidate segments, written as JSONL to
|
|
37
|
+
<material>.segments.jsonl by default; every segment starts label:
|
|
38
|
+
unlabeled, never valid in lint; label each one by hand (claim,
|
|
39
|
+
story, quote, stance, question, aside, private), then run hyperspec
|
|
40
|
+
lint on the spec; --by sentence also starts a segment at each list
|
|
41
|
+
item (-, *, +, 1. or 1) then a space); refuses to overwrite an
|
|
42
|
+
existing file (exit 2); exit 2 for a missing material, a material
|
|
43
|
+
with nothing in it, an --out folder that does not exist, or a --by
|
|
44
|
+
outside paragraph/sentence
|
|
18
45
|
|
|
19
46
|
recipe check <output-or-recipe> [--json]
|
|
20
47
|
check a recipe's completeness (a path not ending .recipe.json
|
|
@@ -47,15 +74,78 @@ const cmd = argv[0];
|
|
|
47
74
|
|
|
48
75
|
if (!cmd || cmd === "--help" || cmd === "-h") { console.log(HELP); process.exit(cmd ? 0 : 2); }
|
|
49
76
|
|
|
77
|
+
// Each profile that wants its own init skeleton adds one entry here; a profile absent from this
|
|
78
|
+
// map still lints (via PROFILES in profiles.mjs) but init falls back to the plain template for it.
|
|
79
|
+
const PROFILE_TEMPLATES = { writing: writingTemplate };
|
|
80
|
+
|
|
50
81
|
if (cmd === "init") {
|
|
51
82
|
const file = argv[1];
|
|
52
83
|
if (!file || file.startsWith("--")) { console.error("init needs a file path"); process.exit(2); }
|
|
53
84
|
if (existsSync(file)) { console.error(`refusing to overwrite ${file}`); process.exit(2); }
|
|
54
|
-
|
|
85
|
+
const usage = (msg) => { console.error(msg); process.exit(2); };
|
|
86
|
+
const given = (name) => argv.includes(name);
|
|
87
|
+
// A value flag with nothing after it, or another flag after it, has no value.
|
|
88
|
+
const value = (name) => { const v = flag(name); return v === undefined || v.startsWith("--") ? undefined : v; };
|
|
89
|
+
const profileName = value("--profile");
|
|
90
|
+
if (given("--profile") && profileName === undefined) usage("--profile needs a value; known profiles: " + Object.keys(PROFILES).join(", "));
|
|
91
|
+
if (profileName !== undefined && !knownProfile(profileName)) {
|
|
92
|
+
usage(`unknown profile: ${profileName}; known profiles: ${Object.keys(PROFILES).join(", ") || "(none)"}`);
|
|
93
|
+
}
|
|
94
|
+
const writeTemplate = profileName !== undefined && Object.hasOwn(PROFILE_TEMPLATES, profileName) ? PROFILE_TEMPLATES[profileName] : undefined;
|
|
95
|
+
// The writing flags mean nothing to the plain template, and --kind means nothing to the writing
|
|
96
|
+
// one (its kind comes from --form); either would be silently dropped, so both are refused.
|
|
97
|
+
if (!writeTemplate) {
|
|
98
|
+
for (const f of ["--fiction", "--form"]) if (given(f)) usage(`${f} only applies with --profile writing`);
|
|
99
|
+
} else if (given("--kind")) {
|
|
100
|
+
usage("--kind does not apply with --profile writing; use --form, which sets kind and writing.form.name together");
|
|
101
|
+
}
|
|
102
|
+
if (given("--form") && value("--form") === undefined) usage("--form needs a value");
|
|
103
|
+
const folder = dirname(resolve(file));
|
|
104
|
+
if (!existsSync(folder) || !statSync(folder).isDirectory()) usage(`the folder ${dirname(file)} does not exist; create it first`);
|
|
105
|
+
const content = writeTemplate
|
|
106
|
+
? writeTemplate({ title: flag("--title"), form: value("--form"), fiction: given("--fiction") })
|
|
107
|
+
: template({ title: flag("--title"), kind: flag("--kind") });
|
|
108
|
+
try { writeFileSync(file, content); }
|
|
109
|
+
catch (e) { usage(`could not write ${file}: ${e.code === "EACCES" ? "permission denied" : e.message}`); }
|
|
55
110
|
console.log(`wrote ${file}; run: hyperspec lint ${file}`);
|
|
56
111
|
process.exit(0);
|
|
57
112
|
}
|
|
58
113
|
|
|
114
|
+
if (cmd === "segments") {
|
|
115
|
+
const sub = argv[1];
|
|
116
|
+
|
|
117
|
+
if (sub === "init") {
|
|
118
|
+
const parsed = parseArgs(argv.slice(2), { valueFlags: ["--id", "--out", "--by"] });
|
|
119
|
+
if (parsed.error) { console.error(parsed.error); process.exit(2); }
|
|
120
|
+
const [material] = parsed.positionals;
|
|
121
|
+
if (!material) { console.error("segments init needs a material path"); process.exit(2); }
|
|
122
|
+
if (!parsed.values["--id"]) { console.error("segments init needs --id <material id>"); process.exit(2); }
|
|
123
|
+
const id = parsed.values["--id"];
|
|
124
|
+
const by = parsed.values["--by"] ?? "paragraph";
|
|
125
|
+
if (by !== "paragraph" && by !== "sentence") { console.error(`--by must be paragraph or sentence, not "${by}"`); process.exit(2); }
|
|
126
|
+
let materialStat;
|
|
127
|
+
try { materialStat = statSync(material); } catch { materialStat = null; }
|
|
128
|
+
if (!materialStat || !materialStat.isFile()) { console.error(`material not found: ${material}`); process.exit(2); }
|
|
129
|
+
const out = parsed.values["--out"] ?? `${material}.segments.jsonl`;
|
|
130
|
+
if (existsSync(out)) { console.error(`refusing to overwrite ${out}`); process.exit(2); }
|
|
131
|
+
const outFolder = dirname(resolve(out));
|
|
132
|
+
if (!existsSync(outFolder) || !statSync(outFolder).isDirectory()) { console.error(`folder does not exist: ${dirname(out)}; create it first`); process.exit(2); }
|
|
133
|
+
|
|
134
|
+
const buf = readFileSync(material);
|
|
135
|
+
const text = buf.toString("utf8");
|
|
136
|
+
if (!/\S/.test(text)) { console.error(`nothing to mark: ${material} has no text`); process.exit(2); }
|
|
137
|
+
const segments = splitSegments(text, { by });
|
|
138
|
+
const header = { material: id, path: material, sha256: sha256(buf) };
|
|
139
|
+
const lines = [JSON.stringify(header), ...segments.map((s) => JSON.stringify(s))];
|
|
140
|
+
writeFileSync(out, `${lines.join("\n")}\n`);
|
|
141
|
+
console.log(`${segments.length} segments written to ${out}. Label every segment (claim, story, quote, stance, question, aside, private), then run hyperspec lint on the spec.`);
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.error(`unknown segments subcommand: ${sub}\n\n${HELP}`);
|
|
146
|
+
process.exit(2);
|
|
147
|
+
}
|
|
148
|
+
|
|
59
149
|
if (cmd === "lint") {
|
|
60
150
|
const json = argv.includes("--json");
|
|
61
151
|
// Lint takes one flag, --json, and no flag takes a value, so a flag is dropped on its own and
|
|
@@ -79,6 +169,7 @@ if (cmd === "lint") {
|
|
|
79
169
|
else for (const r of reports) {
|
|
80
170
|
if (r.error) { console.log(`${r.file}: ${r.error}`); continue; }
|
|
81
171
|
console.log(`${r.file}: ${r.status} (${r.passed}/9)${r.open.length ? `, open: ${r.open.join(", ")}` : ""}`);
|
|
172
|
+
if (r.profile) console.log(` ${r.profile.name}: ${r.profile.complete}/${r.profile.total} blocks complete`);
|
|
82
173
|
for (const t of r.tests) if (!t.pass) console.log(` ✗ ${t.n}. ${t.name}`);
|
|
83
174
|
for (const f of r.findings) console.log(` ${f.severity === "fail" ? "fail" : "warn"} [${f.test}] ${f.message}\n fix: ${f.fix}`);
|
|
84
175
|
}
|
|
@@ -316,7 +407,7 @@ if (cmd === "compare") {
|
|
|
316
407
|
if (json) console.log(JSON.stringify(result, null, 2));
|
|
317
408
|
|
|
318
409
|
// Both a usage error and a check-failed error (missing output, escaping path, ...) map to
|
|
319
|
-
// 2 here
|
|
410
|
+
// 2 here: compare's own ok:false without a usage flag is still "could not grade", i.e. an
|
|
320
411
|
// unreadable-input class failure, not a graded-but-worse-1 class one.
|
|
321
412
|
if (!result.ok) {
|
|
322
413
|
if (!json) console.error(result.error);
|
|
@@ -7,7 +7,7 @@ decisions:
|
|
|
7
7
|
state: decided
|
|
8
8
|
value: the operator, reading on a phone
|
|
9
9
|
source: interview A2
|
|
10
|
-
author:
|
|
10
|
+
author: example-author
|
|
11
11
|
chosen_by: human
|
|
12
12
|
- id: length
|
|
13
13
|
state: delegated
|
|
@@ -22,7 +22,7 @@ requirements:
|
|
|
22
22
|
check:
|
|
23
23
|
rubric: ask the simulated reader to define the term; pass only on a correct definition
|
|
24
24
|
source: design doc, audience block
|
|
25
|
-
author:
|
|
25
|
+
author: example-author
|
|
26
26
|
rejects:
|
|
27
27
|
- hype words about AI
|
|
28
28
|
examples:
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Interview notes, a thirty-minute call with an engineering manager of eight years, taken by the
|
|
2
|
+
author during the call. Considered: the manager reviewed these notes afterwards and corrected two
|
|
3
|
+
lines.
|
|
4
|
+
|
|
5
|
+
- Her rule: the report owns the agenda. She keeps a shared document per person; they add items
|
|
6
|
+
before the meeting, and she adds hers last, at the bottom.
|
|
7
|
+
- "If I have something urgent, it is not a one-on-one topic. I send it the day it happens."
|
|
8
|
+
- The first one-on-one with a new report is always the same: she asks how they like to receive
|
|
9
|
+
feedback, in writing or out loud, right away or at the end of the week.
|
|
10
|
+
- Her warning: new managers treat silence as a problem. "Wait. Count to five. The real answer is
|
|
11
|
+
the second one."
|
|
12
|
+
- She cancels a one-on-one only when the report asks her to, never on her own.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{"material":"interview","path":"essay/materials/interview-notes.md","sha256":"3c4fd3ff19435027b1bb9584e648128d59feb3184f67eeaf6b09ed9fa8bcfaeb"}
|
|
2
|
+
{"id":"s1","start":0,"end":118,"label":"aside","text":"Interview notes, a thirty-minute call with an engineering manager of eight years, taken by the\nauthor during the call."}
|
|
3
|
+
{"id":"s2","start":119,"end":199,"label":"aside","text":"Considered: the manager reviewed these notes afterwards and corrected two\nlines."}
|
|
4
|
+
{"id":"s3","start":201,"end":240,"label":"claim","source":"interview with an engineering manager of eight years","text":"- Her rule: the report owns the agenda."}
|
|
5
|
+
{"id":"s4","start":241,"end":356,"label":"claim","source":"interview with an engineering manager of eight years","text":"She keeps a shared document per person; they add items\n before the meeting, and she adds hers last, at the bottom."}
|
|
6
|
+
{"id":"s5","start":357,"end":448,"label":"quote","speaker":"the engineering manager interviewed","text":"- \"If I have something urgent, it is not a one-on-one topic. I send it the day it happens.\""}
|
|
7
|
+
{"id":"s6","start":449,"end":617,"label":"claim","source":"interview with an engineering manager of eight years","text":"- The first one-on-one with a new report is always the same: she asks how they like to receive\n feedback, in writing or out loud, right away or at the end of the week."}
|
|
8
|
+
{"id":"s7","start":618,"end":673,"label":"claim","source":"interview with an engineering manager of eight years","text":"- Her warning: new managers treat silence as a problem."}
|
|
9
|
+
{"id":"s8","start":674,"end":733,"label":"quote","speaker":"the engineering manager interviewed","text":"\"Wait. Count to five. The real answer is\n the second one.\""}
|
|
10
|
+
{"id":"s9","start":734,"end":812,"label":"claim","source":"interview with an engineering manager of eight years","text":"- She cancels a one-on-one only when the report asks her to, never on her own."}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Summary of an internal survey the author's team ran in the spring, 41 responses, figures checked
|
|
2
|
+
against the raw export by a second person. Verified.
|
|
3
|
+
|
|
4
|
+
- 29 of 41 said their most useful one-on-one in the last quarter was one where they brought the
|
|
5
|
+
first topic.
|
|
6
|
+
- 11 of 41 said at least one of their one-on-ones in the last quarter was mostly project status.
|
|
7
|
+
- The most common free-text request, in 9 responses: "ask me what I want to work on next."
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{"material":"survey","path":"essay/materials/team-survey.md","sha256":"ae2c821b1731ad02eb3280c28872aa9d2163728b868f262554d0cbfd3da19b97"}
|
|
2
|
+
{"id":"s1","start":0,"end":139,"label":"aside","text":"Summary of an internal survey the author's team ran in the spring, 41 responses, figures checked\nagainst the raw export by a second person."}
|
|
3
|
+
{"id":"s2","start":140,"end":149,"label":"aside","text":"Verified."}
|
|
4
|
+
{"id":"s3","start":151,"end":261,"label":"claim","source":"team survey, spring, 41 responses, figures checked against the raw export","text":"- 29 of 41 said their most useful one-on-one in the last quarter was one where they brought the\n first topic."}
|
|
5
|
+
{"id":"s4","start":262,"end":358,"label":"claim","source":"team survey, spring, 41 responses, figures checked against the raw export","text":"- 11 of 41 said at least one of their one-on-ones in the last quarter was mostly project status."}
|
|
6
|
+
{"id":"s5","start":359,"end":449,"label":"claim","source":"team survey, spring, 41 responses, figures checked against the raw export","text":"- The most common free-text request, in 9 responses: \"ask me what I want to work on next.\""}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Voice memo transcript, recorded by the author on a walk, lightly cleaned. Raw thinking.
|
|
2
|
+
|
|
3
|
+
My first one-on-one as a manager was a disaster, and the reason was simple: I ran it. I had a
|
|
4
|
+
list. I went down the list. Project status, blockers, the thing from Tuesday. Thirty minutes
|
|
5
|
+
later my report said "cool, thanks" and left, and I had learned nothing I could not have read in
|
|
6
|
+
the tracker.
|
|
7
|
+
|
|
8
|
+
What I wish someone had told me: the first one-on-one is the only meeting where they get to set
|
|
9
|
+
the agenda. If you set it, you have told them what the meeting is for, and it is for you.
|
|
10
|
+
|
|
11
|
+
The three questions I use now. What is taking more of your energy than it should? What do you
|
|
12
|
+
want to be doing more of in six months? What should I stop doing, or start doing, that would
|
|
13
|
+
make your week easier? Then I shut up.
|
|
14
|
+
|
|
15
|
+
Status goes in the tracker. If a one-on-one is a status meeting, cancel it and read the tracker.
|
|
16
|
+
|
|
17
|
+
Aside, probably not for this piece: my second manager used to walk the one-on-ones outside. I
|
|
18
|
+
liked it but I do not think it is the point.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{"material":"voice-memo","path":"essay/materials/voice-memo.md","sha256":"c1e07e7d6886f0f3abe638500b614a94883b66f259d272b213f7fe63e70afdc8"}
|
|
2
|
+
{"id":"s1","start":0,"end":87,"label":"aside","text":"Voice memo transcript, recorded by the author on a walk, lightly cleaned. Raw thinking."}
|
|
3
|
+
{"id":"s2","start":89,"end":385,"label":"story","teller":"example-author","text":"My first one-on-one as a manager was a disaster, and the reason was simple: I ran it. I had a\nlist. I went down the list. Project status, blockers, the thing from Tuesday. Thirty minutes\nlater my report said \"cool, thanks\" and left, and I had learned nothing I could not have read in\nthe tracker."}
|
|
4
|
+
{"id":"s3","start":387,"end":572,"label":"claim","own":true,"text":"What I wish someone had told me: the first one-on-one is the only meeting where they get to set\nthe agenda. If you set it, you have told them what the meeting is for, and it is for you."}
|
|
5
|
+
{"id":"s4","start":574,"end":799,"label":"claim","own":true,"text":"The three questions I use now. What is taking more of your energy than it should? What do you\nwant to be doing more of in six months? What should I stop doing, or start doing, that would\nmake your week easier? Then I shut up."}
|
|
6
|
+
{"id":"s5","start":801,"end":897,"label":"stance","text":"Status goes in the tracker. If a one-on-one is a status meeting, cancel it and read the tracker."}
|
|
7
|
+
{"id":"s6","start":899,"end":1037,"label":"aside","text":"Aside, probably not for this piece: my second manager used to walk the one-on-ones outside. I\nliked it but I do not think it is the point."}
|
|
File without changes
|
|
@@ -0,0 +1,220 @@
|
|
|
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: outline the four spine claims against the form's required parts, citing the segments each claim points at
|
|
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
|
+
segments: essay/materials/voice-memo.md.segments.jsonl
|
|
89
|
+
produced_by: example-author
|
|
90
|
+
captured: "2026-09-12"
|
|
91
|
+
how: voice memo, transcribed
|
|
92
|
+
trust: raw
|
|
93
|
+
- id: interview
|
|
94
|
+
path: essay/materials/interview-notes.md
|
|
95
|
+
segments: essay/materials/interview-notes.md.segments.jsonl
|
|
96
|
+
produced_by: example-author
|
|
97
|
+
captured: "2026-09-15"
|
|
98
|
+
how: notes taken during a call, reviewed by the person interviewed
|
|
99
|
+
trust: considered
|
|
100
|
+
- id: survey
|
|
101
|
+
path: essay/materials/team-survey.md
|
|
102
|
+
segments: essay/materials/team-survey.md.segments.jsonl
|
|
103
|
+
produced_by: example-author
|
|
104
|
+
captured: "2026-05-30"
|
|
105
|
+
how: survey summary, figures checked against the raw export by a second person
|
|
106
|
+
trust: verified
|
|
107
|
+
check:
|
|
108
|
+
station: every segment of every material carries a label from the closed set, matches its source verbatim, and the markings are current
|
|
109
|
+
source: capture step
|
|
110
|
+
author: agent:claude
|
|
111
|
+
dna:
|
|
112
|
+
writer: example-author
|
|
113
|
+
scope:
|
|
114
|
+
form: essay
|
|
115
|
+
audience: new managers
|
|
116
|
+
purpose: teach
|
|
117
|
+
rules: style-rules.md
|
|
118
|
+
goldens:
|
|
119
|
+
- path: essay/goldens/opening.md
|
|
120
|
+
why: one plain claim, then a second sentence that turns it into something to do
|
|
121
|
+
- path: essay/goldens/close.md
|
|
122
|
+
why: ends on an instruction and gives the reason for it in the same sentence
|
|
123
|
+
check:
|
|
124
|
+
rubric: blind lineup within this scope; a judge shown the generated opening beside the two goldens cannot pick it out
|
|
125
|
+
source: goldens marked on the review page
|
|
126
|
+
author: example-author
|
|
127
|
+
persona:
|
|
128
|
+
identity: self
|
|
129
|
+
stance: mentor
|
|
130
|
+
may_assert:
|
|
131
|
+
- what the author did in their own first one-on-ones and what happened
|
|
132
|
+
- the three questions the author uses now
|
|
133
|
+
will_not_say:
|
|
134
|
+
- a claim about what most managers do, beyond the survey's own figures
|
|
135
|
+
- the name of anyone on the author's team
|
|
136
|
+
facts_from: sources
|
|
137
|
+
check:
|
|
138
|
+
rubric: persona-consistency judge; the mentor stance holds, and no fact appears that is not in the claims ledger
|
|
139
|
+
source: persona interview
|
|
140
|
+
author: example-author
|
|
141
|
+
audience:
|
|
142
|
+
who: someone in their first three months of managing, who was promoted from the team they now lead
|
|
143
|
+
funnel_now: has a first one-on-one with a new report on the calendar this week
|
|
144
|
+
knows:
|
|
145
|
+
- one-on-one
|
|
146
|
+
- report
|
|
147
|
+
- tracker
|
|
148
|
+
believes_now: a one-on-one is where a manager catches up on how the work is going
|
|
149
|
+
wants: a plan for the first meeting that will not waste either person's half hour
|
|
150
|
+
reads_on: a phone, in the ten minutes before the meeting
|
|
151
|
+
reader: person
|
|
152
|
+
check:
|
|
153
|
+
station: term check against knows; any other term is defined on first use
|
|
154
|
+
rubric: simulated reader reports where it got lost and where it stopped reading
|
|
155
|
+
source: audience interview
|
|
156
|
+
author: example-author
|
|
157
|
+
goal:
|
|
158
|
+
from: plans to run the first one-on-one from their own list
|
|
159
|
+
to: hands the first one-on-one to the report and asks the three questions
|
|
160
|
+
next_if_worked: copies the three questions into their calendar invite
|
|
161
|
+
change:
|
|
162
|
+
kind: action
|
|
163
|
+
text: the reader asks the three questions in their next one-on-one and waits after each
|
|
164
|
+
conditions: [r1, r2, r3, r4, r5, r6]
|
|
165
|
+
check:
|
|
166
|
+
rubric: the doctor grades the draft against every condition; the simulated reader is asked whether it would copy the questions now
|
|
167
|
+
source: goal interview
|
|
168
|
+
author: example-author
|
|
169
|
+
form:
|
|
170
|
+
name: essay
|
|
171
|
+
length:
|
|
172
|
+
min: 700
|
|
173
|
+
max: 1100
|
|
174
|
+
unit: words
|
|
175
|
+
required_parts:
|
|
176
|
+
- an opening that states the claim
|
|
177
|
+
- the story of the author's first one-on-one
|
|
178
|
+
- the three questions
|
|
179
|
+
- what to do with the answers
|
|
180
|
+
- a close the reader can act on
|
|
181
|
+
stations:
|
|
182
|
+
- the three questions render as a numbered list
|
|
183
|
+
check:
|
|
184
|
+
station: structure and length check against required_parts and length
|
|
185
|
+
source: form decision
|
|
186
|
+
author: example-author
|
|
187
|
+
spine:
|
|
188
|
+
kind: primer
|
|
189
|
+
claims:
|
|
190
|
+
- id: c1
|
|
191
|
+
text: the first one-on-one is the one meeting where the report should set the agenda
|
|
192
|
+
materials: [voice-memo#s3, interview#s3]
|
|
193
|
+
- id: c2
|
|
194
|
+
text: status belongs in the tracker, and a one-on-one spent on it teaches the manager nothing new
|
|
195
|
+
materials: [voice-memo#s2, voice-memo#s5, survey#s4]
|
|
196
|
+
- id: c3
|
|
197
|
+
text: three questions are enough to hand the meeting over
|
|
198
|
+
materials: [voice-memo#s4]
|
|
199
|
+
- id: c4
|
|
200
|
+
text: the answer worth having comes after a silence the manager does not fill
|
|
201
|
+
materials: [interview#s7, interview#s8]
|
|
202
|
+
check:
|
|
203
|
+
rubric: each claim lands, in order, and the draft argues nothing outside the chain
|
|
204
|
+
source: spine interview
|
|
205
|
+
author: example-author
|
|
206
|
+
sources:
|
|
207
|
+
ledger: essay/claims.jsonl
|
|
208
|
+
unsourced_claim: fail
|
|
209
|
+
check:
|
|
210
|
+
station: every factual claim in the ledger points at a source span; every quote matches its source verbatim
|
|
211
|
+
source: sourcing pass
|
|
212
|
+
author: agent:claude
|
|
213
|
+
fiction: false
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
# Hand your first one-on-one to the person you manage
|
|
217
|
+
|
|
218
|
+
A worked example of the writing profile: an essay for new managers, specified before a word of
|
|
219
|
+
it is drafted. Every file this spec names ships beside it. `essay/claims.jsonl` does not exist
|
|
220
|
+
yet, because the claims ledger is written during drafting.
|
|
@@ -0,0 +1,9 @@
|
|
|
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.
|
|
9
|
+
- Said in confidence, not for the story: the lease ends next spring, and she has not told her staff.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{"material":"bakery-visit","path":"story/materials/bakery-visit.md","sha256":"5eb09f7c0ad8836378c549392d939f0b070669e972c9e4d94e1f81f27465a19b"}
|
|
2
|
+
{"id":"s1","start":0,"end":90,"label":"aside","text":"Notes from a morning spent at a working bakery, 3:30 to 7:30, with the owner's permission."}
|
|
3
|
+
{"id":"s2","start":91,"end":163,"label":"aside","text":"Considered: the owner read these notes and corrected the proofing times."}
|
|
4
|
+
{"id":"s3","start":165,"end":185,"label":"claim","source":"a morning observed at a working bakery, notes corrected by the owner","text":"- First mix at 3:45."}
|
|
5
|
+
{"id":"s4","start":186,"end":255,"label":"claim","source":"a morning observed at a working bakery, notes corrected by the owner","text":"Rye sourdough proofs about three hours at room temperature in winter."}
|
|
6
|
+
{"id":"s5","start":256,"end":346,"label":"claim","source":"a morning observed at a working bakery, notes corrected by the owner","text":"- Trays are turned halfway through the bake because the back left of a deck oven runs hot."}
|
|
7
|
+
{"id":"s6","start":347,"end":385,"label":"claim","source":"a morning observed at a working bakery, notes corrected by the owner","text":"- The doors open to customers at 7:00."}
|
|
8
|
+
{"id":"s7","start":386,"end":427,"label":"claim","source":"a morning observed at a working bakery, notes corrected by the owner","text":"The first person in is usually a regular."}
|
|
9
|
+
{"id":"s8","start":428,"end":468,"label":"claim","source":"a morning observed at a working bakery, notes corrected by the owner","text":"- The owner does not talk while shaping."}
|
|
10
|
+
{"id":"s9","start":469,"end":514,"label":"claim","source":"a morning observed at a working bakery, notes corrected by the owner","text":"Talking happens at the mixer and at the till."}
|
|
11
|
+
{"id":"s10","start":515,"end":549,"label":"claim","source":"a morning observed at a working bakery, notes corrected by the owner","text":"- Flour is weighed, never scooped."}
|
|
12
|
+
{"id":"s11","start":550,"end":591,"label":"claim","source":"a morning observed at a working bakery, notes corrected by the owner","text":"Water temperature is checked every batch."}
|
|
13
|
+
{"id":"s12","start":592,"end":692,"label":"private","text":"- Said in confidence, not for the story: the lease ends next spring, and she has not told her staff."}
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
Open question: does she tell him about the sale before she lets him shape the rye, or after?
|
|
12
|
+
|
|
13
|
+
What it is about, I think: a craft outlives the room it was practiced in. And: people who love
|
|
14
|
+
each other in a work setting say it through the work.
|
|
15
|
+
|
|
16
|
+
The last line should be about bread, never about feelings.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{"material":"notes","path":"story/materials/notes.md","sha256":"d2af0977ca30dc1f666d219e3c5d2d0f063e91d9d07f14cf703f848ca260ada8"}
|
|
2
|
+
{"id":"s1","start":0,"end":68,"label":"aside","text":"Author's notes for the story, typed over two evenings. Raw thinking."}
|
|
3
|
+
{"id":"s2","start":70,"end":405,"label":"claim","own":true,"text":"A bakery on its last morning before the sale closes. Two people: the owner, who has run it for\nthirty-one years, and the apprentice she took on at sixteen, now nineteen. She has not told him\nit is sold. He has not told her he got into a baking school in another city and leaves in the\nautumn. Each thinks they are protecting the other."}
|
|
4
|
+
{"id":"s3","start":407,"end":534,"label":"claim","own":true,"text":"The story is four scenes, one morning, 3:40 to 7:00. The oven has a noise. The rye is the thing\nshe has never let him do alone."}
|
|
5
|
+
{"id":"s4","start":536,"end":628,"label":"question","text":"Open question: does she tell him about the sale before she lets him shape the rye, or after?"}
|
|
6
|
+
{"id":"s5","start":630,"end":778,"label":"stance","text":"What it is about, I think: a craft outlives the room it was practiced in. And: people who love\neach other in a work setting say it through the work."}
|
|
7
|
+
{"id":"s6","start":780,"end":838,"label":"stance","text":"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.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{"material":"scene-list","path":"story/materials/scene-list.md","sha256":"8181cdb35cd1afad01809367de15b3233f24fc7bede5b7b89c022f05e3d655e0"}
|
|
2
|
+
{"id":"s1","start":0,"end":51,"label":"aside","text":"Scene list, agreed with the editor before drafting."}
|
|
3
|
+
{"id":"s2","start":52,"end":63,"label":"aside","text":"Considered."}
|
|
4
|
+
{"id":"s3","start":65,"end":95,"label":"claim","own":true,"text":"- scene-1, 3:40: Theo arrives."}
|
|
5
|
+
{"id":"s4","start":96,"end":119,"label":"claim","own":true,"text":"Ines is already mixing."}
|
|
6
|
+
{"id":"s5","start":120,"end":135,"label":"claim","own":true,"text":"The oven noise."}
|
|
7
|
+
{"id":"s6","start":136,"end":162,"label":"claim","own":true,"text":"No one says anything real."}
|
|
8
|
+
{"id":"s7","start":163,"end":188,"label":"claim","own":true,"text":"- scene-2, 4:30: shaping."}
|
|
9
|
+
{"id":"s8","start":189,"end":259,"label":"claim","own":true,"text":"Ines lets Theo shape the rye for the first time, and does not say why."}
|
|
10
|
+
{"id":"s9","start":260,"end":286,"label":"claim","own":true,"text":"- scene-3, 5:50: the bake."}
|
|
11
|
+
{"id":"s10","start":287,"end":344,"label":"claim","own":true,"text":"Ines tells Theo the bakery is sold, while turning a tray."}
|
|
12
|
+
{"id":"s11","start":345,"end":384,"label":"claim","own":true,"text":"- scene-4, 6:55: before the doors open."}
|
|
13
|
+
{"id":"s12","start":385,"end":418,"label":"claim","own":true,"text":"Theo tells Ines about the school."}
|
|
14
|
+
{"id":"s13","start":419,"end":451,"label":"claim","own":true,"text":"She hands him the\n starter jar."}
|
|
File without changes
|