@supersuit/hyperspec 0.8.0 → 0.9.1

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 (37) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +46 -5
  3. package/SPEC.md +2 -2
  4. package/WRITING.md +369 -25
  5. package/bin/hyperspec.mjs +185 -13
  6. package/examples/writing/course/part-1.md +17 -0
  7. package/examples/writing/course/part-2.md +17 -0
  8. package/examples/writing/course.hyperspec.md +1 -0
  9. package/examples/writing/essay/judge/panel-buyer.packet.json +118 -0
  10. package/examples/writing/essay/judge/panel-expert.packet.json +114 -0
  11. package/examples/writing/essay/judge/panel-novice.packet.json +114 -0
  12. package/examples/writing/essay/judge/panel-skeptic.packet.json +114 -0
  13. package/examples/writing/essay/sample-verdicts/panel-buyer.verdict.json +11 -0
  14. package/examples/writing/essay/sample-verdicts/panel-expert.verdict.json +13 -0
  15. package/examples/writing/essay/sample-verdicts/panel-novice.verdict.json +11 -0
  16. package/examples/writing/essay/sample-verdicts/panel-skeptic.verdict.json +13 -0
  17. package/examples/writing/story/judge/panel-buyer.packet.json +118 -0
  18. package/examples/writing/story/judge/panel-expert.packet.json +114 -0
  19. package/examples/writing/story/judge/panel-novice.packet.json +114 -0
  20. package/examples/writing/story/judge/panel-skeptic.packet.json +114 -0
  21. package/examples/writing/story/sample-verdicts/panel-buyer.verdict.json +13 -0
  22. package/examples/writing/story/sample-verdicts/panel-expert.verdict.json +11 -0
  23. package/examples/writing/story/sample-verdicts/panel-novice.verdict.json +11 -0
  24. package/examples/writing/story/sample-verdicts/panel-skeptic.verdict.json +11 -0
  25. package/package.json +1 -1
  26. package/src/evidence.mjs +74 -0
  27. package/src/judge.mjs +50 -66
  28. package/src/judges/index.mjs +7 -1
  29. package/src/judges/panel.mjs +135 -0
  30. package/src/segments.mjs +44 -0
  31. package/src/stations/index.mjs +3 -1
  32. package/src/stations/quotes.mjs +26 -2
  33. package/src/stations/sequence.mjs +149 -2
  34. package/src/stations/triage.mjs +20 -0
  35. package/src/triage.mjs +401 -0
  36. package/src/writing-fields.mjs +48 -1
  37. package/src/writing.mjs +5 -1
package/bin/hyperspec.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, statSync, writeFileSync, readFileSync, mkdirSync } from "node:fs";
2
+ import { existsSync, statSync, writeFileSync, readFileSync, mkdirSync, realpathSync } from "node:fs";
3
3
  import { dirname, resolve, join } from "node:path";
4
4
  import { loadSpec } from "../src/load.mjs";
5
5
  import { lintSpec } from "../src/rules.mjs";
@@ -12,13 +12,15 @@ import { approve } from "../src/writer.mjs";
12
12
  import { reproduce } from "../src/reproduce.mjs";
13
13
  import { regenerate } from "../src/regenerate.mjs";
14
14
  import { compare } from "../src/compare.mjs";
15
- import { splitSegments } from "../src/segments.mjs";
15
+ import { splitSegments, carrySegments } from "../src/segments.mjs";
16
16
  import { sha256 } from "../src/hash.mjs";
17
17
  import { readScope, measureFeatures, writeFeatures, scopeTemplate, GOLDENS_README } from "../src/dna.mjs";
18
18
  import { str } from "../src/placeholder.mjs";
19
19
  import { runCheck } from "../src/check.mjs";
20
20
  import { prepareJudges, recordJudgment } from "../src/judge.mjs";
21
21
  import { prepareLearn, recordLearn, tallyLine } from "../src/learn.mjs";
22
+ import { triageContext, triageState, answerFinding, importReview, replyText } from "../src/triage.mjs";
23
+ import { sourceAt } from "../src/sequence-draft.mjs";
22
24
 
23
25
  const HELP = `hyperspec <command> [options]
24
26
 
@@ -51,7 +53,10 @@ const HELP = `hyperspec <command> [options]
51
53
  persona (against the claims ledger), and for fiction attribution (a
52
54
  blind speaker test on the dialogue lines whose speaker the draft
53
55
  names; the answers go to attribution.key.json, likewise rebuilt)
54
- and knowledge (each character's knowledge timeline);
56
+ and knowledge (each character's knowledge timeline), and panel
57
+ (one panel-<reader>.packet.json per reader: writing.panel's, or a
58
+ skeptic, a novice and an expert, and always the audience's reader
59
+ as buyer);
55
60
  the same spec and draft give byte-identical packets; refuses to
56
61
  overwrite an existing packet without --force
57
62
  exit 0 written, 1 a station could not build its packet (the rest
@@ -65,7 +70,28 @@ const HELP = `hyperspec <command> [options]
65
70
  verdict appends nothing; run it from the folder prepare ran in, since
66
71
  the packet keeps the spec and draft paths as they were given
67
72
  exit 0 the station passed, 1 it failed or the verdict is invalid
68
- or stale, 2 usage
73
+ or stale, 2 usage; a panel verdict's improve, missing and remove
74
+ items go to triage.jsonl, beside the runs ledger
75
+ triage status <spec> [--draft <file>] [--json]
76
+ count the answers in the spec's triage.jsonl, list the passages two
77
+ or more readers share, and hold every answer to the draft (as
78
+ check's triage station does); without --draft a spec that lists
79
+ writing.form.sequence.files reads them, as check does
80
+ exit 0 it would pass, 1 it would fail, 2 usage
81
+ triage answer <spec> <finding> taken|kept|already-true|open [--evidence S] [--reason S]
82
+ [--draft <file>] [--json]
83
+ answer one finding: taken and already-true need --evidence, a
84
+ passage of the draft word for word; kept needs --reason
85
+ exit 0 written, 1 refused (nothing written), 2 usage
86
+ triage import <spec> <review> [--source S] [--draft <file>] [--json]
87
+ bring an outside review (markdown or plain text) in as findings;
88
+ a heading names the reader, Good/Improve/Missing/Remove labels set
89
+ the kind, each list item is a finding, praise is counted only
90
+ exit 0 imported, 1 the review holds nothing to answer, 2 usage
91
+ triage reply <spec> [--source S] [--draft <file>] [--json]
92
+ print a plain-text reply to the reviewer from the answers; it
93
+ never sends anything
94
+ exit 0 ready to send, 1 a finding is still unanswered, 2 usage
69
95
  learn prepare <spec> --first <draft> --approved <draft> --out <dir> [--force] [--json]
70
96
  needs a writing spec that passes lint (exits with lint's own code
71
97
  otherwise); diffs the first draft a factory produced against the
@@ -99,16 +125,21 @@ const HELP = `hyperspec <command> [options]
99
125
  writing, --kind with it (use --form), or a folder that does not
100
126
  exist
101
127
 
102
- segments init <material> --id <mid> [--out <file>] [--by paragraph|sentence]
128
+ segments init <material> --id <mid> [--out <file>] [--by paragraph|sentence] [--keep <old>]
103
129
  split a material into candidate segments, written as JSONL to
104
130
  <material>.segments.jsonl by default; every segment starts label:
105
131
  unlabeled, never valid in lint; label each one by hand (claim,
106
132
  story, quote, stance, question, aside, private), then run hyperspec
107
133
  lint on the spec; --by sentence also starts a segment at each list
108
- item (-, *, +, 1. or 1) then a space); refuses to overwrite an
109
- existing file (exit 2); exit 2 for a missing material, a material
110
- with nothing in it, an --out folder that does not exist, or a --by
111
- outside paragraph/sentence
134
+ item (-, *, +, 1. or 1) then a space); --keep re-marks an edited
135
+ material: every segment whose text, trimmed, an old segment in
136
+ <old> has keeps that segment's id, label and every other key, and
137
+ the rest are listed to label; refuses to overwrite an existing
138
+ file unless --keep names it (exit 2); exit 2 for a missing
139
+ material, a material with nothing in it, an --out folder that does
140
+ not exist, a --by outside paragraph/sentence, or a --keep file that
141
+ cannot be read, has a line that is not a JSON object, or marks
142
+ another material
112
143
 
113
144
  dna init <scope-dir> --writer W --form F --audience A --purpose P
114
145
  write a new writer-DNA scope: scope.md (writer, form, audience,
@@ -199,7 +230,7 @@ if (cmd === "segments") {
199
230
  const sub = argv[1];
200
231
 
201
232
  if (sub === "init") {
202
- const parsed = parseArgs(argv.slice(2), { valueFlags: ["--id", "--out", "--by"] });
233
+ const parsed = parseArgs(argv.slice(2), { valueFlags: ["--id", "--out", "--by", "--keep"] });
203
234
  if (parsed.error) { console.error(parsed.error); process.exit(2); }
204
235
  const [material] = parsed.positionals;
205
236
  if (!material) { console.error("segments init needs a material path"); process.exit(2); }
@@ -211,18 +242,55 @@ if (cmd === "segments") {
211
242
  try { materialStat = statSync(material); } catch { materialStat = null; }
212
243
  if (!materialStat || !materialStat.isFile()) { console.error(`material not found: ${material}`); process.exit(2); }
213
244
  const out = parsed.values["--out"] ?? `${material}.segments.jsonl`;
214
- if (existsSync(out)) { console.error(`refusing to overwrite ${out}`); process.exit(2); }
245
+ const keep = parsed.values["--keep"];
246
+ // --keep may name the file being written: re-marking in place is the point. Nothing else is
247
+ // ever overwritten.
248
+ const samePath = (a, b) => { try { return realpathSync(a) === realpathSync(b); } catch { return resolve(a) === resolve(b); } };
249
+ if (existsSync(out) && !(keep && samePath(keep, out))) { console.error(`refusing to overwrite ${out}${keep ? " (--keep names a different file)" : ""}`); process.exit(2); }
215
250
  const outFolder = dirname(resolve(out));
216
251
  if (!existsSync(outFolder) || !statSync(outFolder).isDirectory()) { console.error(`folder does not exist: ${dirname(out)}; create it first`); process.exit(2); }
217
252
 
253
+ // The old segments to carry labels from: every line after the header, each a JSON object.
254
+ let old = null;
255
+ if (keep) {
256
+ let raw;
257
+ try { raw = readFileSync(keep, "utf8"); } catch { console.error(`--keep file not found: ${keep}`); process.exit(2); }
258
+ const rows = raw.split("\n").map((l, i) => ({ n: i + 1, l })).filter((r) => r.l.trim());
259
+ old = [];
260
+ for (const { n, l } of rows) {
261
+ let o;
262
+ try { o = JSON.parse(l); } catch { o = null; }
263
+ if (!o || typeof o !== "object" || Array.isArray(o)) { console.error(`--keep file ${keep} line ${n} is not a JSON object; fix it, or mark the material from scratch`); process.exit(2); }
264
+ if (n === 1) {
265
+ if (str(o.material) && str(o.material) !== id) { console.error(`--keep file ${keep} marks material "${o.material}", not "${id}"`); process.exit(2); }
266
+ continue;
267
+ }
268
+ old.push(o);
269
+ }
270
+ }
271
+
218
272
  const buf = readFileSync(material);
219
273
  const text = buf.toString("utf8");
220
274
  if (!/\S/.test(text)) { console.error(`nothing to mark: ${material} has no text`); process.exit(2); }
221
- const segments = splitSegments(text, { by });
275
+ const fresh = splitSegments(text, { by });
276
+ const kept = old ? carrySegments(fresh, old) : null;
277
+ const segments = kept ? kept.segments : fresh;
222
278
  const header = { material: id, path: material, sha256: sha256(buf) };
223
279
  const lines = [JSON.stringify(header), ...segments.map((s) => JSON.stringify(s))];
224
280
  writeFileSync(out, `${lines.join("\n")}\n`);
225
- 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.`);
281
+ if (!kept) {
282
+ 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.`);
283
+ process.exit(0);
284
+ }
285
+ const todo = kept.unlabeled;
286
+ console.log(`${segments.length} segments written to ${out}, ${kept.carried} labels carried from ${keep}, ${todo.length} to label${todo.length ? ":" : "."}`);
287
+ for (const s of todo) {
288
+ const t = s.text.replace(/\s+/g, " ").trim();
289
+ console.log(` ${s.id} ${JSON.stringify(t.length > 60 ? `${t.slice(0, 60)}...` : t)}`);
290
+ }
291
+ console.log(todo.length
292
+ ? "Label each one (claim, story, quote, stance, question, aside, private), then run hyperspec lint on the spec."
293
+ : "Run hyperspec lint on the spec.");
226
294
  process.exit(0);
227
295
  }
228
296
 
@@ -521,6 +589,110 @@ if (cmd === "learn") {
521
589
  process.exit(2);
522
590
  }
523
591
 
592
+ if (cmd === "triage") {
593
+ const sub = argv[1];
594
+ const flags = {
595
+ status: { valueFlags: ["--draft"], boolFlags: ["--json"] },
596
+ answer: { valueFlags: ["--draft", "--evidence", "--reason"], boolFlags: ["--json"] },
597
+ import: { valueFlags: ["--draft", "--source"], boolFlags: ["--json"] },
598
+ reply: { valueFlags: ["--draft", "--source"], boolFlags: ["--json"] },
599
+ }[sub];
600
+ if (!flags) { console.error(`unknown triage subcommand: ${sub ?? "(none)"}\n\n${HELP}`); process.exit(2); }
601
+ const parsed = parseArgs(argv.slice(2), flags);
602
+ if (parsed.error) { console.error(parsed.error); process.exit(2); }
603
+ const [specPath, ...rest] = parsed.positionals;
604
+ const json = parsed.values["--json"];
605
+ const usage = (error) => {
606
+ if (json) console.log(JSON.stringify({ spec: specPath ?? null, draft: parsed.values["--draft"] ?? null, error }, null, 2));
607
+ else console.error(error);
608
+ process.exit(2);
609
+ };
610
+ const ctx = triageContext(specPath, parsed.values["--draft"], `triage ${sub}`);
611
+ if (ctx.usage) usage(ctx.error);
612
+ const { spec, draft } = ctx;
613
+ // A finding's line, named the way check names it: the file and its own line for a sequence.
614
+ const where = (f) => {
615
+ if (typeof f.line !== "number") return "";
616
+ const src = sourceAt(draft, f.line);
617
+ return src ? ` (${src.file} line ${f.line - src.startLine + 1})` : ` (line ${f.line})`;
618
+ };
619
+ const printFinding = (f) => console.log(` ${f.severity === "fail" ? "fail" : "warn"} [${f.id}] ${f.message}${where(f)}\n fix: ${f.fix}`);
620
+
621
+ if (sub === "status") {
622
+ const state = triageState(spec, draft);
623
+ if (state.skip) {
624
+ if (json) console.log(JSON.stringify({ spec: specPath, status: "skip", reason: state.skip }, null, 2));
625
+ else console.log(`triage: skip (${state.skip})`);
626
+ process.exit(0);
627
+ }
628
+ const status = state.findings.some((f) => f.severity === "fail") ? "fail" : "pass";
629
+ if (json) console.log(JSON.stringify({ spec: specPath, path: state.decl, status, counts: state.counts, shared: state.shared, findings: state.findings, items: state.items }, null, 2));
630
+ else {
631
+ const c = state.counts;
632
+ console.log(`${state.decl}: ${state.items.length} finding${state.items.length === 1 ? "" : "s"}; ${c.taken} taken, ${c.kept} kept, ${c["already-true"]} already true, ${c.open} open, ${c.unanswered} not answered`);
633
+ if (state.shared.length) {
634
+ console.log("shared by two or more readers:");
635
+ for (const g of state.shared) {
636
+ const line = where({ line: g.line }).replace(/^ \((.*)\)$/, "$1");
637
+ console.log(` ${line}: "${g.passage}"`);
638
+ for (const f of g.findings) console.log(` ${f.reader} (${f.kind}): ${f.text}`);
639
+ }
640
+ }
641
+ console.log(`triage: ${status}`);
642
+ for (const f of state.findings) printFinding(f);
643
+ }
644
+ process.exit(status === "pass" ? 0 : 1);
645
+ }
646
+
647
+ if (sub === "answer") {
648
+ const [findingId, disposition] = rest;
649
+ if (!findingId) usage("triage answer needs a finding id");
650
+ if (!disposition) usage("triage answer needs a disposition: taken, kept, already-true or open");
651
+ const result = answerFinding(spec, draft, findingId, disposition, { evidence: parsed.values["--evidence"], reason: parsed.values["--reason"] });
652
+ if (result.usage) usage(result.error);
653
+ if (json) console.log(JSON.stringify(result, null, 2));
654
+ else if (result.invalid) {
655
+ console.log(`${findingId}: answer refused, nothing written`);
656
+ for (const f of result.findings) printFinding(f);
657
+ } else console.log(`${findingId}: ${disposition} (${result.path})`);
658
+ process.exit(result.code);
659
+ }
660
+
661
+ if (sub === "import") {
662
+ const [reviewPath] = rest;
663
+ if (!reviewPath) usage("triage import needs a review file");
664
+ let text;
665
+ try { text = readFileSync(resolve(reviewPath), "utf8"); } catch { usage(`cannot read review: ${reviewPath}`); }
666
+ const source = parsed.values["--source"] ?? reviewPath;
667
+ const result = importReview(spec, draft, text, source);
668
+ if (result.usage) usage(result.error);
669
+ if (json) console.log(JSON.stringify(result, null, 2));
670
+ else if (result.invalid) {
671
+ console.log(`${reviewPath}: nothing imported`);
672
+ for (const f of result.findings) printFinding(f);
673
+ } else {
674
+ console.log(`${source}: ${result.added} finding${result.added === 1 ? "" : "s"} added to triage (${result.path})${result.already ? `, ${result.already} already there` : ""}${result.praise ? `; ${result.praise} item${result.praise === 1 ? "" : "s"} of praise, not triaged` : ""}`);
675
+ for (const f of result.warnings) printFinding(f);
676
+ }
677
+ process.exit(result.code);
678
+ }
679
+
680
+ if (sub === "reply") {
681
+ const state = triageState(spec, draft);
682
+ if (state.skip) usage(`nothing to reply to: ${state.skip}`);
683
+ const source = parsed.values["--source"];
684
+ const reply = replyText(state.items, source);
685
+ if (!reply.count) usage(`no finding in ${state.decl} comes from ${source}`);
686
+ const failing = state.findings.filter((f) => f.severity === "fail");
687
+ if (json) console.log(JSON.stringify({ spec: specPath, source: source ?? null, text: reply.text, unanswered: reply.unanswered, ready: !failing.length }, null, 2));
688
+ else {
689
+ process.stdout.write(reply.text);
690
+ if (failing.length) console.error(`not ready to send: triage fails (${failing.length} finding${failing.length === 1 ? "" : "s"}); run hyperspec triage status`);
691
+ }
692
+ process.exit(failing.length ? 1 : 0);
693
+ }
694
+ }
695
+
524
696
  // A spec that does not lint clean: print what lint would, say nothing was written, and exit with
525
697
  // lint's own code. Shared by judge prepare and learn prepare.
526
698
  function printLintBlocked(result, json, nothingWritten) {
@@ -42,6 +42,23 @@ Add a spoon of starter to the dough from Lesson 1 and leave it somewhere warm. T
42
42
 
43
43
  **Try this:** start a starter today, and mark the jar with tape at its height after each feed.
44
44
 
45
+ ## Check yourself: Part 1
46
+
47
+ 1. *(Lesson 1)* Five hundred grams of flour and four hundred of water: what is the hydration?
48
+ - a) Forty percent
49
+ - b) Eighty percent
50
+ 2. *(Lesson 1)* When is flour and water a dough?
51
+ - a) When no dry flour is left
52
+ - b) When it has doubled
53
+ 3. *(Lesson 2)* What does a starter need every day?
54
+ - a) A pinch of salt
55
+ - b) A feed of flour and water
56
+ 4. *(Lesson 2)* How do you tell a proof is done?
57
+ - a) A finger dent fills back slowly
58
+ - b) The top has cracked
59
+
60
+ **Answers:** 1 b · 2 a · 3 b · 4 a
61
+
45
62
  ---
46
63
 
47
64
  **Next, Part 2: The bake.** Shaping a loaf that holds itself up, and what happens to the crumb in the oven.
@@ -38,3 +38,20 @@ Heat the oven as hot as it goes, with a heavy pot inside. Put the loaf in the po
38
38
  Let it cool for an hour before you cut it. Then read the crumb. Big uneven holes mean a well proofed, high hydration dough. A tight, even crumb with a dense band at the bottom means the proof was too short.
39
39
 
40
40
  **Try this:** cut the two loaves from Lesson 3 and compare their crumb.
41
+
42
+ ## Check yourself: Part 2
43
+
44
+ 1. *(Lesson 3)* Why give a dough a bench rest?
45
+ - a) So it relaxes enough to be shaped again
46
+ - b) So it cools down
47
+ 2. *(Lesson 3)* What lets a shaped loaf stand instead of spreading?
48
+ - a) More water
49
+ - b) Surface tension
50
+ 3. *(Lesson 4)* When does oven spring happen?
51
+ - a) In the first ten minutes of heat
52
+ - b) While the loaf cools
53
+ 4. *(Lesson 4)* A tight, even crumb with a dense band at the bottom means what?
54
+ - a) The oven was too hot
55
+ - b) The proof was too short
56
+
57
+ **Answers:** 1 a · 2 b · 3 a · 4 b
@@ -160,6 +160,7 @@ writing:
160
160
  terms_section: New terms
161
161
  outline: course/outline.md
162
162
  teaser: Next,
163
+ quiz: Check yourself
163
164
  check:
164
165
  station: structure and length, then the sequence station
165
166
  source: form decision
@@ -0,0 +1,118 @@
1
+ {
2
+ "hyperspec_judge": "0.1",
3
+ "station": "panel",
4
+ "spec": "essay.hyperspec.md",
5
+ "spec_sha256": "e03dfac6b445ae779000f7fc109a9d713df2e5cf8967faf8e808fe87d63e40e9",
6
+ "draft": "essay/draft.md",
7
+ "draft_sha256": "aa4a1d99604f7b2b398b67305b368abbb72651356c38bfffad1452db6be62f65",
8
+ "rubric": "simulated reader reports where it got lost and where it stopped reading",
9
+ "instructions": "Read inputs.draft as inputs.reader: the person inputs.reader.who describes, knowing only what inputs.reader.knows lists and what anyone would, reading for inputs.reader.lens. List what works for this reader under good, what should change under improve, what this reader needs that the draft does not give under missing, and what should go under remove. Every item is { evidence, note }. evidence is a passage copied verbatim from inputs.draft, at least three whole words; for a missing item, quote the passage nearest to where the missing thing belongs. note says what and why, in a sentence, in this reader's terms. A list may be empty. Report what this reader would say, not what another reader would. Answer only in the verdict shape given in verdict_schema.",
10
+ "inputs": {
11
+ "reader": {
12
+ "id": "buyer",
13
+ "who": "someone in their first three months of managing, who was promoted from the team they now lead",
14
+ "knows": [
15
+ "one-on-one",
16
+ "report",
17
+ "tracker"
18
+ ],
19
+ "lens": "what they want from this piece: a plan for the first meeting that will not waste either person's half hour"
20
+ },
21
+ "draft": "# Hand your first one-on-one to the person you manage\n\n## Who sets the agenda\n\nYour first one-on-one with a new report is the only meeting on your calendar where they should\nset the agenda. Everything else you run. This one you hand over.\n\nIf you walk in with a list, you have told your report what the meeting is for, and it is for\nyou. They will answer your list politely and leave. You will know nothing you did not know when\nyou sat down, and so will they.\n\nMy team ran a [survey](materials/team-survey.md) this spring. 29 of 41 people said their most useful one-on-one in the last\nquarter was one where they brought the first topic. The meeting that worked for them was the one\nthey started.\n\nSo give them the start. The simplest way to do it is a running agenda: one shared document per\nperson, kept for as long as you manage them. Your report adds items before each meeting, and you\nadd yours last, at the bottom. That is how an engineering manager I interviewed, eight years into\nthe job, runs hers. Her rule is short: the report owns the agenda.\n\n## My first one-on-one\n\nMy first one-on-one as a manager was a disaster, and the reason was simple: I ran it. I had a\nlist, and I went down the list. Project status, blockers, the thing from Tuesday. Thirty minutes\nlater my report said thanks and left, and I had learned nothing I could not have read in the\ntracker.\n\nWhat I ran was a status meeting, which is a meeting spent reading out loud what the tracker\nalready says. Your report wrote those updates. Asking them to recite the updates to you teaches\nyou nothing new, and it spends the one half hour a week that belongs to them.\n\nThe survey shows the cost from their side. 11 of 41 said at least one of their one-on-ones in the\nlast quarter was mostly project status. Read the tracker before you walk in, and leave status\nthere.\n\n## The three questions\n\nHere is what I ask now, in this order, and then I let the report take over:\n\n1. What is taking more of your energy than it should?\n2. What do you want to be doing more of in six months?\n3. What should I stop doing, or start doing, that would make your week easier?\n\nThree is enough to hand the meeting over. The first asks about this week. The second asks about\nthe next six months. The third asks about you, and it is the one your report will not raise\nwithout being asked. A fourth question starts to look like your list again, and the list is what\nyou came to give up.\n\nAsk them in the same words every time. Your report will learn them, and after a few weeks they\nwill walk in with answers already half formed. That is the point of fixing the words: the meeting\nstarts on their topic before you have said anything at all.\n\nThe second question is the one my own team asked for. The most common free-text request in the\nsurvey, in 9 responses, was to be asked what they want to work on next.\n\nAfter the third question, stop talking.\n\n## What to do with the answers\n\nWait after each question, longer than you want to. Dana, the engineering manager I interviewed,\nputs it in three short sentences: \"Wait. Count to five. The real answer is the second one.\" The first\nanswer your report gives is the tidy one, the version they could give anyone. The second is what\nthey came in with, and you only hear it if you let the silence run.\n\nWrite down what they say, in their words, at the top of the running agenda. That list is where\nyour next one-on-one starts, so the meeting stays theirs the week after too.\n\nDo not try to fix everything in the room. Pick one thing you can act on this week, say what you\nwill do, and do it before you meet again. The rest stays on the running agenda until it is done\nor your report takes it off.\n\nAnd keep your own urgent items out of it. She told me: \"If I have something urgent, it is not a\none-on-one topic. I send it the day it happens.\" Your report should never have to wait a week to\nhear something you needed them to know on Monday.\n\n## Before the meeting\n\nOpen the invite and delete your list from it. Ask your report to add the first item to the\nrunning agenda instead.\n\nSo write the three questions on a card. Ask the first one. Then wait, longer than feels polite,\nbecause the first answer is the one they rehearsed and the second one is the one you came for.\n"
22
+ },
23
+ "verdict_schema": {
24
+ "type": "object",
25
+ "required": [
26
+ "good",
27
+ "improve",
28
+ "missing",
29
+ "remove"
30
+ ],
31
+ "properties": {
32
+ "good": {
33
+ "type": "array",
34
+ "description": "what works for this reader; empty when nothing does",
35
+ "items": {
36
+ "type": "object",
37
+ "required": [
38
+ "evidence",
39
+ "note"
40
+ ],
41
+ "properties": {
42
+ "evidence": {
43
+ "type": "string",
44
+ "description": "a passage copied verbatim from the draft, at least three words; for a missing item, the passage nearest where it belongs"
45
+ },
46
+ "note": {
47
+ "type": "string",
48
+ "description": "what and why, in a sentence"
49
+ }
50
+ }
51
+ }
52
+ },
53
+ "improve": {
54
+ "type": "array",
55
+ "description": "what should change",
56
+ "items": {
57
+ "type": "object",
58
+ "required": [
59
+ "evidence",
60
+ "note"
61
+ ],
62
+ "properties": {
63
+ "evidence": {
64
+ "type": "string",
65
+ "description": "a passage copied verbatim from the draft, at least three words; for a missing item, the passage nearest where it belongs"
66
+ },
67
+ "note": {
68
+ "type": "string",
69
+ "description": "what and why, in a sentence"
70
+ }
71
+ }
72
+ }
73
+ },
74
+ "missing": {
75
+ "type": "array",
76
+ "description": "what this reader needs that the draft does not give",
77
+ "items": {
78
+ "type": "object",
79
+ "required": [
80
+ "evidence",
81
+ "note"
82
+ ],
83
+ "properties": {
84
+ "evidence": {
85
+ "type": "string",
86
+ "description": "a passage copied verbatim from the draft, at least three words; for a missing item, the passage nearest where it belongs"
87
+ },
88
+ "note": {
89
+ "type": "string",
90
+ "description": "what and why, in a sentence"
91
+ }
92
+ }
93
+ }
94
+ },
95
+ "remove": {
96
+ "type": "array",
97
+ "description": "what should go",
98
+ "items": {
99
+ "type": "object",
100
+ "required": [
101
+ "evidence",
102
+ "note"
103
+ ],
104
+ "properties": {
105
+ "evidence": {
106
+ "type": "string",
107
+ "description": "a passage copied verbatim from the draft, at least three words; for a missing item, the passage nearest where it belongs"
108
+ },
109
+ "note": {
110
+ "type": "string",
111
+ "description": "what and why, in a sentence"
112
+ }
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,114 @@
1
+ {
2
+ "hyperspec_judge": "0.1",
3
+ "station": "panel",
4
+ "spec": "essay.hyperspec.md",
5
+ "spec_sha256": "e03dfac6b445ae779000f7fc109a9d713df2e5cf8967faf8e808fe87d63e40e9",
6
+ "draft": "essay/draft.md",
7
+ "draft_sha256": "aa4a1d99604f7b2b398b67305b368abbb72651356c38bfffad1452db6be62f65",
8
+ "rubric": "simulated reader reports where it got lost and where it stopped reading",
9
+ "instructions": "Read inputs.draft as inputs.reader: the person inputs.reader.who describes, knowing only what inputs.reader.knows lists and what anyone would, reading for inputs.reader.lens. List what works for this reader under good, what should change under improve, what this reader needs that the draft does not give under missing, and what should go under remove. Every item is { evidence, note }. evidence is a passage copied verbatim from inputs.draft, at least three whole words; for a missing item, quote the passage nearest to where the missing thing belongs. note says what and why, in a sentence, in this reader's terms. A list may be empty. Report what this reader would say, not what another reader would. Answer only in the verdict shape given in verdict_schema.",
10
+ "inputs": {
11
+ "reader": {
12
+ "id": "expert",
13
+ "who": "an expert in the piece's subject",
14
+ "knows": [],
15
+ "lens": "what is wrong, out of date, oversimplified or missing for someone who knows the field"
16
+ },
17
+ "draft": "# Hand your first one-on-one to the person you manage\n\n## Who sets the agenda\n\nYour first one-on-one with a new report is the only meeting on your calendar where they should\nset the agenda. Everything else you run. This one you hand over.\n\nIf you walk in with a list, you have told your report what the meeting is for, and it is for\nyou. They will answer your list politely and leave. You will know nothing you did not know when\nyou sat down, and so will they.\n\nMy team ran a [survey](materials/team-survey.md) this spring. 29 of 41 people said their most useful one-on-one in the last\nquarter was one where they brought the first topic. The meeting that worked for them was the one\nthey started.\n\nSo give them the start. The simplest way to do it is a running agenda: one shared document per\nperson, kept for as long as you manage them. Your report adds items before each meeting, and you\nadd yours last, at the bottom. That is how an engineering manager I interviewed, eight years into\nthe job, runs hers. Her rule is short: the report owns the agenda.\n\n## My first one-on-one\n\nMy first one-on-one as a manager was a disaster, and the reason was simple: I ran it. I had a\nlist, and I went down the list. Project status, blockers, the thing from Tuesday. Thirty minutes\nlater my report said thanks and left, and I had learned nothing I could not have read in the\ntracker.\n\nWhat I ran was a status meeting, which is a meeting spent reading out loud what the tracker\nalready says. Your report wrote those updates. Asking them to recite the updates to you teaches\nyou nothing new, and it spends the one half hour a week that belongs to them.\n\nThe survey shows the cost from their side. 11 of 41 said at least one of their one-on-ones in the\nlast quarter was mostly project status. Read the tracker before you walk in, and leave status\nthere.\n\n## The three questions\n\nHere is what I ask now, in this order, and then I let the report take over:\n\n1. What is taking more of your energy than it should?\n2. What do you want to be doing more of in six months?\n3. What should I stop doing, or start doing, that would make your week easier?\n\nThree is enough to hand the meeting over. The first asks about this week. The second asks about\nthe next six months. The third asks about you, and it is the one your report will not raise\nwithout being asked. A fourth question starts to look like your list again, and the list is what\nyou came to give up.\n\nAsk them in the same words every time. Your report will learn them, and after a few weeks they\nwill walk in with answers already half formed. That is the point of fixing the words: the meeting\nstarts on their topic before you have said anything at all.\n\nThe second question is the one my own team asked for. The most common free-text request in the\nsurvey, in 9 responses, was to be asked what they want to work on next.\n\nAfter the third question, stop talking.\n\n## What to do with the answers\n\nWait after each question, longer than you want to. Dana, the engineering manager I interviewed,\nputs it in three short sentences: \"Wait. Count to five. The real answer is the second one.\" The first\nanswer your report gives is the tidy one, the version they could give anyone. The second is what\nthey came in with, and you only hear it if you let the silence run.\n\nWrite down what they say, in their words, at the top of the running agenda. That list is where\nyour next one-on-one starts, so the meeting stays theirs the week after too.\n\nDo not try to fix everything in the room. Pick one thing you can act on this week, say what you\nwill do, and do it before you meet again. The rest stays on the running agenda until it is done\nor your report takes it off.\n\nAnd keep your own urgent items out of it. She told me: \"If I have something urgent, it is not a\none-on-one topic. I send it the day it happens.\" Your report should never have to wait a week to\nhear something you needed them to know on Monday.\n\n## Before the meeting\n\nOpen the invite and delete your list from it. Ask your report to add the first item to the\nrunning agenda instead.\n\nSo write the three questions on a card. Ask the first one. Then wait, longer than feels polite,\nbecause the first answer is the one they rehearsed and the second one is the one you came for.\n"
18
+ },
19
+ "verdict_schema": {
20
+ "type": "object",
21
+ "required": [
22
+ "good",
23
+ "improve",
24
+ "missing",
25
+ "remove"
26
+ ],
27
+ "properties": {
28
+ "good": {
29
+ "type": "array",
30
+ "description": "what works for this reader; empty when nothing does",
31
+ "items": {
32
+ "type": "object",
33
+ "required": [
34
+ "evidence",
35
+ "note"
36
+ ],
37
+ "properties": {
38
+ "evidence": {
39
+ "type": "string",
40
+ "description": "a passage copied verbatim from the draft, at least three words; for a missing item, the passage nearest where it belongs"
41
+ },
42
+ "note": {
43
+ "type": "string",
44
+ "description": "what and why, in a sentence"
45
+ }
46
+ }
47
+ }
48
+ },
49
+ "improve": {
50
+ "type": "array",
51
+ "description": "what should change",
52
+ "items": {
53
+ "type": "object",
54
+ "required": [
55
+ "evidence",
56
+ "note"
57
+ ],
58
+ "properties": {
59
+ "evidence": {
60
+ "type": "string",
61
+ "description": "a passage copied verbatim from the draft, at least three words; for a missing item, the passage nearest where it belongs"
62
+ },
63
+ "note": {
64
+ "type": "string",
65
+ "description": "what and why, in a sentence"
66
+ }
67
+ }
68
+ }
69
+ },
70
+ "missing": {
71
+ "type": "array",
72
+ "description": "what this reader needs that the draft does not give",
73
+ "items": {
74
+ "type": "object",
75
+ "required": [
76
+ "evidence",
77
+ "note"
78
+ ],
79
+ "properties": {
80
+ "evidence": {
81
+ "type": "string",
82
+ "description": "a passage copied verbatim from the draft, at least three words; for a missing item, the passage nearest where it belongs"
83
+ },
84
+ "note": {
85
+ "type": "string",
86
+ "description": "what and why, in a sentence"
87
+ }
88
+ }
89
+ }
90
+ },
91
+ "remove": {
92
+ "type": "array",
93
+ "description": "what should go",
94
+ "items": {
95
+ "type": "object",
96
+ "required": [
97
+ "evidence",
98
+ "note"
99
+ ],
100
+ "properties": {
101
+ "evidence": {
102
+ "type": "string",
103
+ "description": "a passage copied verbatim from the draft, at least three words; for a missing item, the passage nearest where it belongs"
104
+ },
105
+ "note": {
106
+ "type": "string",
107
+ "description": "what and why, in a sentence"
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }