@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.
- package/CHANGELOG.md +83 -0
- package/README.md +46 -5
- package/SPEC.md +2 -2
- package/WRITING.md +369 -25
- package/bin/hyperspec.mjs +185 -13
- package/examples/writing/course/part-1.md +17 -0
- package/examples/writing/course/part-2.md +17 -0
- package/examples/writing/course.hyperspec.md +1 -0
- package/examples/writing/essay/judge/panel-buyer.packet.json +118 -0
- package/examples/writing/essay/judge/panel-expert.packet.json +114 -0
- package/examples/writing/essay/judge/panel-novice.packet.json +114 -0
- package/examples/writing/essay/judge/panel-skeptic.packet.json +114 -0
- package/examples/writing/essay/sample-verdicts/panel-buyer.verdict.json +11 -0
- package/examples/writing/essay/sample-verdicts/panel-expert.verdict.json +13 -0
- package/examples/writing/essay/sample-verdicts/panel-novice.verdict.json +11 -0
- package/examples/writing/essay/sample-verdicts/panel-skeptic.verdict.json +13 -0
- package/examples/writing/story/judge/panel-buyer.packet.json +118 -0
- package/examples/writing/story/judge/panel-expert.packet.json +114 -0
- package/examples/writing/story/judge/panel-novice.packet.json +114 -0
- package/examples/writing/story/judge/panel-skeptic.packet.json +114 -0
- package/examples/writing/story/sample-verdicts/panel-buyer.verdict.json +13 -0
- package/examples/writing/story/sample-verdicts/panel-expert.verdict.json +11 -0
- package/examples/writing/story/sample-verdicts/panel-novice.verdict.json +11 -0
- package/examples/writing/story/sample-verdicts/panel-skeptic.verdict.json +11 -0
- package/package.json +1 -1
- package/src/evidence.mjs +74 -0
- package/src/judge.mjs +50 -66
- package/src/judges/index.mjs +7 -1
- package/src/judges/panel.mjs +135 -0
- package/src/segments.mjs +44 -0
- package/src/stations/index.mjs +3 -1
- package/src/stations/quotes.mjs +26 -2
- package/src/stations/sequence.mjs +149 -2
- package/src/stations/triage.mjs +20 -0
- package/src/triage.mjs +401 -0
- package/src/writing-fields.mjs +48 -1
- 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);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
@@ -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
|
+
}
|