@supersuit/hyperspec 0.4.0 → 0.6.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 +143 -0
- package/README.md +50 -2
- package/SPEC.md +5 -5
- package/WRITING.md +510 -17
- package/bin/hyperspec.mjs +177 -2
- package/examples/writing/dna/essay-new-managers-teach/features.json +56 -0
- package/examples/writing/dna/essay-new-managers-teach/goldens/README.md +14 -0
- package/examples/writing/dna/essay-new-managers-teach/goldens/close.md +9 -0
- package/examples/writing/dna/essay-new-managers-teach/goldens/opening.md +9 -0
- package/examples/writing/dna/essay-new-managers-teach/goldens/status.md +10 -0
- package/examples/writing/dna/essay-new-managers-teach/scope.md +11 -0
- package/examples/writing/essay/claims.jsonl +9 -0
- package/examples/writing/essay/draft.md +82 -0
- package/examples/writing/essay/materials/interview-notes.md.segments.jsonl +2 -2
- package/examples/writing/essay.hyperspec.md +32 -13
- package/examples/writing/story/claims.jsonl +9 -0
- package/examples/writing/story/draft.md +267 -0
- package/examples/writing/story.hyperspec.md +20 -5
- package/package.json +1 -1
- package/src/check.mjs +245 -0
- package/src/dna.mjs +474 -0
- package/src/stations/claims.mjs +150 -0
- package/src/stations/dna.mjs +126 -0
- package/src/stations/form.mjs +115 -0
- package/src/stations/index.mjs +27 -0
- package/src/stations/links.mjs +275 -0
- package/src/stations/private.mjs +117 -0
- package/src/stations/quotes.mjs +168 -0
- package/src/stations/terms.mjs +131 -0
- package/src/stations/util.mjs +99 -0
- package/src/writing-exports.mjs +8 -3
- package/src/writing-fields.mjs +197 -1
- package/src/writing-template.mjs +8 -0
- package/examples/writing/essay/goldens/close.md +0 -2
- package/examples/writing/essay/goldens/opening.md +0 -2
package/bin/hyperspec.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, statSync, writeFileSync, readFileSync } from "node:fs";
|
|
3
|
-
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { existsSync, statSync, writeFileSync, readFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve, join } from "node:path";
|
|
4
4
|
import { loadSpec } from "../src/load.mjs";
|
|
5
5
|
import { lintSpec } from "../src/rules.mjs";
|
|
6
6
|
import { score, exitCode } from "../src/score.mjs";
|
|
@@ -14,11 +14,27 @@ import { regenerate } from "../src/regenerate.mjs";
|
|
|
14
14
|
import { compare } from "../src/compare.mjs";
|
|
15
15
|
import { splitSegments } from "../src/segments.mjs";
|
|
16
16
|
import { sha256 } from "../src/hash.mjs";
|
|
17
|
+
import { readScope, measureFeatures, writeFeatures, scopeTemplate, GOLDENS_README } from "../src/dna.mjs";
|
|
18
|
+
import { str } from "../src/placeholder.mjs";
|
|
19
|
+
import { runCheck } from "../src/check.mjs";
|
|
17
20
|
|
|
18
21
|
const HELP = `hyperspec <command> [options]
|
|
19
22
|
|
|
20
23
|
lint <file...> [--json] score each hyperspec against the nine tests
|
|
21
24
|
exit 0 pass, 1 a test fails, 3 blocked on an open decision, 2 usage
|
|
25
|
+
check <spec> --draft <file> [--json] [--only a,b]
|
|
26
|
+
needs a writing spec (profile: writing); lints it first (a spec
|
|
27
|
+
that does not pass lint, or is blocked, exits with lint's own code
|
|
28
|
+
and runs no station: a draft cannot be checked against a spec that
|
|
29
|
+
is not ready); then runs every deterministic station (or the
|
|
30
|
+
--only subset, by name) against the draft, printing pass, fail
|
|
31
|
+
(with findings) or skip (with a reason) per station; appends one
|
|
32
|
+
line to the spec's improvement.ledger with a verdict: one-shot,
|
|
33
|
+
improved, or not-improved with a reason (an --only run is partial:
|
|
34
|
+
not-improved, and ignored by later verdicts)
|
|
35
|
+
exit 0 every run station passed, 1 a station failed, 2 usage
|
|
36
|
+
(including a missing draft file, a spec without the writing
|
|
37
|
+
profile, or an --only that names no known station)
|
|
22
38
|
init <file> [--title T] [--kind K] write a new hyperspec skeleton (refuses to overwrite)
|
|
23
39
|
init <file> --profile writing [--title T] [--form F] [--fiction]
|
|
24
40
|
write a writing-profile skeleton: every required block (materials,
|
|
@@ -43,6 +59,23 @@ const HELP = `hyperspec <command> [options]
|
|
|
43
59
|
with nothing in it, an --out folder that does not exist, or a --by
|
|
44
60
|
outside paragraph/sentence
|
|
45
61
|
|
|
62
|
+
dna init <scope-dir> --writer W --form F --audience A --purpose P
|
|
63
|
+
write a new writer-DNA scope: scope.md (writer, form, audience,
|
|
64
|
+
purpose) and an empty goldens/ folder holding a README on the
|
|
65
|
+
golden file shape (why, approved_by, source, optional approved_on;
|
|
66
|
+
the body is the passage, verbatim); refuses to overwrite an
|
|
67
|
+
existing scope.md; exit 2 on a missing parent folder, a missing
|
|
68
|
+
flag, or a flag value that looks like a placeholder (todo, ..., a
|
|
69
|
+
bare -), never a stack trace
|
|
70
|
+
dna measure <scope-dir> [--json]
|
|
71
|
+
read every golden in <scope-dir>/goldens/, check its required
|
|
72
|
+
fields, and write <scope-dir>/features.json: deterministic style
|
|
73
|
+
features measured from the goldens' text, never judged and never
|
|
74
|
+
run through a model
|
|
75
|
+
exit 0 wrote features.json, 1 a golden (or the scope itself) fails
|
|
76
|
+
a required-field check, so a hollow golden is never measured into
|
|
77
|
+
the DNA, 2 usage
|
|
78
|
+
|
|
46
79
|
recipe check <output-or-recipe> [--json]
|
|
47
80
|
check a recipe's completeness (a path not ending .recipe.json
|
|
48
81
|
means <path>.recipe.json)
|
|
@@ -146,6 +179,93 @@ if (cmd === "segments") {
|
|
|
146
179
|
process.exit(2);
|
|
147
180
|
}
|
|
148
181
|
|
|
182
|
+
if (cmd === "dna") {
|
|
183
|
+
const sub = argv[1];
|
|
184
|
+
|
|
185
|
+
if (sub === "init") {
|
|
186
|
+
const parsed = parseArgs(argv.slice(2), { valueFlags: ["--writer", "--form", "--audience", "--purpose"] });
|
|
187
|
+
if (parsed.error) { console.error(parsed.error); process.exit(2); }
|
|
188
|
+
const [scopeDir] = parsed.positionals;
|
|
189
|
+
if (!scopeDir) { console.error("dna init needs a scope-dir path"); process.exit(2); }
|
|
190
|
+
for (const flagName of ["--writer", "--form", "--audience", "--purpose"]) {
|
|
191
|
+
if (!parsed.values[flagName]) { console.error(`dna init needs ${flagName} <value>`); process.exit(2); }
|
|
192
|
+
}
|
|
193
|
+
// A placeholder-looking value (todo, tbd, ..., ???, ...) is caught here rather than left
|
|
194
|
+
// for the next `dna measure` to catch on scope.md's own fields; str() is the one place that
|
|
195
|
+
// pattern is defined (src/placeholder.mjs), reused rather than re-checked.
|
|
196
|
+
for (const flagName of ["--writer", "--form", "--audience", "--purpose"]) {
|
|
197
|
+
if (!str(parsed.values[flagName])) {
|
|
198
|
+
console.error(`dna init ${flagName} "${parsed.values[flagName]}" looks like a placeholder; give it real content`);
|
|
199
|
+
process.exit(2);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const writer = parsed.values["--writer"];
|
|
203
|
+
const form = parsed.values["--form"];
|
|
204
|
+
const audience = parsed.values["--audience"];
|
|
205
|
+
const purpose = parsed.values["--purpose"];
|
|
206
|
+
|
|
207
|
+
const scopeAbs = resolve(scopeDir);
|
|
208
|
+
const parent = dirname(scopeAbs);
|
|
209
|
+
if (!existsSync(parent) || !statSync(parent).isDirectory()) {
|
|
210
|
+
console.error(`the folder ${dirname(scopeDir)} does not exist; create it first`);
|
|
211
|
+
process.exit(2);
|
|
212
|
+
}
|
|
213
|
+
if (existsSync(scopeAbs) && !statSync(scopeAbs).isDirectory()) {
|
|
214
|
+
console.error(`${scopeDir} is not a directory`);
|
|
215
|
+
process.exit(2);
|
|
216
|
+
}
|
|
217
|
+
// scopeMdPath (absolute, resolved from the cwd) is for filesystem operations only. Every
|
|
218
|
+
// message uses scopeMdDisplay, built from scopeDir exactly as given (relative, if that is how
|
|
219
|
+
// the operator typed it): a path the operator did not resolve themselves must never appear
|
|
220
|
+
// resolved in output, the same rule every other finding in this linter already follows.
|
|
221
|
+
const scopeMdPath = join(scopeAbs, "scope.md");
|
|
222
|
+
const scopeMdDisplay = join(scopeDir, "scope.md");
|
|
223
|
+
if (existsSync(scopeMdPath)) { console.error(`refusing to overwrite ${scopeMdDisplay}`); process.exit(2); }
|
|
224
|
+
|
|
225
|
+
mkdirSync(join(scopeAbs, "goldens"), { recursive: true });
|
|
226
|
+
writeFileSync(scopeMdPath, scopeTemplate({ writer, form, audience, purpose }));
|
|
227
|
+
// An operator's own goldens/README.md is theirs: write the guidance only where none exists.
|
|
228
|
+
try {
|
|
229
|
+
writeFileSync(join(scopeAbs, "goldens", "README.md"), GOLDENS_README, { flag: "wx" });
|
|
230
|
+
} catch (err) {
|
|
231
|
+
if (err.code !== "EEXIST") throw err;
|
|
232
|
+
}
|
|
233
|
+
console.log(`wrote ${scopeMdDisplay} and ${scopeDir}/goldens/. Add goldens, then run: hyperspec dna measure ${scopeDir}`);
|
|
234
|
+
process.exit(0);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (sub === "measure") {
|
|
238
|
+
const parsed = parseArgs(argv.slice(2), { boolFlags: ["--json"] });
|
|
239
|
+
if (parsed.error) { console.error(parsed.error); process.exit(2); }
|
|
240
|
+
const [scopeDir] = parsed.positionals;
|
|
241
|
+
if (!scopeDir) { console.error("dna measure needs a scope-dir path"); process.exit(2); }
|
|
242
|
+
const json = parsed.values["--json"];
|
|
243
|
+
|
|
244
|
+
const { scope, goldens, findings } = readScope(scopeDir);
|
|
245
|
+
const hasFail = findings.some((x) => x.severity === "fail");
|
|
246
|
+
if (hasFail) {
|
|
247
|
+
if (json) console.log(JSON.stringify({ scope: scopeDir, findings }, null, 2));
|
|
248
|
+
else for (const x of findings) console.log(`fail [${x.test}] ${x.message}\n fix: ${x.fix}`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const features = measureFeatures(goldens.map((g) => g.text));
|
|
253
|
+
const { path: featuresPath } = writeFeatures(scopeDir, { scope, goldens, features });
|
|
254
|
+
if (json) {
|
|
255
|
+
console.log(JSON.stringify({ scope: scopeDir, goldens: goldens.length, features, wrote: featuresPath }, null, 2));
|
|
256
|
+
} else {
|
|
257
|
+
console.log(`${scopeDir}: measured ${goldens.length} golden${goldens.length === 1 ? "" : "s"}`);
|
|
258
|
+
console.log(` word_count ${features.word_count}, sentence length mean ${features.sentence_length.mean} median ${features.sentence_length.median} p90 ${features.sentence_length.p90}`);
|
|
259
|
+
console.log(` signature words: ${features.signature_words.join(", ") || "(none)"}`);
|
|
260
|
+
console.log(`wrote ${featuresPath}`);
|
|
261
|
+
}
|
|
262
|
+
process.exit(0);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
console.error(`unknown dna subcommand: ${sub}\n\n${HELP}`);
|
|
266
|
+
process.exit(2);
|
|
267
|
+
}
|
|
268
|
+
|
|
149
269
|
if (cmd === "lint") {
|
|
150
270
|
const json = argv.includes("--json");
|
|
151
271
|
// Lint takes one flag, --json, and no flag takes a value, so a flag is dropped on its own and
|
|
@@ -176,6 +296,61 @@ if (cmd === "lint") {
|
|
|
176
296
|
process.exit(worst);
|
|
177
297
|
}
|
|
178
298
|
|
|
299
|
+
if (cmd === "check") {
|
|
300
|
+
const parsed = parseArgs(argv.slice(1), { valueFlags: ["--draft", "--only"], boolFlags: ["--json"] });
|
|
301
|
+
if (parsed.error) { console.error(parsed.error); process.exit(2); }
|
|
302
|
+
const [specPath] = parsed.positionals;
|
|
303
|
+
const json = parsed.values["--json"];
|
|
304
|
+
// A usage error exits 2: a plain message on stderr, or under --json one document on stdout,
|
|
305
|
+
// { spec, draft, error }, the way lint --json reports a file it could not read.
|
|
306
|
+
const usage = (error) => {
|
|
307
|
+
if (json) console.log(JSON.stringify({ spec: specPath ?? null, draft: parsed.values["--draft"] ?? null, error }, null, 2));
|
|
308
|
+
else console.error(error);
|
|
309
|
+
process.exit(2);
|
|
310
|
+
};
|
|
311
|
+
if (!specPath) usage("check needs a spec path");
|
|
312
|
+
if (!parsed.values["--draft"]) usage("check needs --draft <file>");
|
|
313
|
+
let only;
|
|
314
|
+
if (parsed.values["--only"] !== undefined) {
|
|
315
|
+
only = parsed.values["--only"].split(",").map((s) => s.trim()).filter(Boolean);
|
|
316
|
+
if (!only.length) usage("--only names no station");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const result = runCheck(specPath, parsed.values["--draft"], { only });
|
|
320
|
+
|
|
321
|
+
// Usage errors (an unreadable spec, a spec without the writing profile, an unknown --only name,
|
|
322
|
+
// a missing draft file): exit 2, as above.
|
|
323
|
+
if (result.usage) usage(result.error);
|
|
324
|
+
|
|
325
|
+
if (result.lintBlocked) {
|
|
326
|
+
if (json) console.log(JSON.stringify(result, null, 2));
|
|
327
|
+
else {
|
|
328
|
+
const r = result.lintScore;
|
|
329
|
+
console.log(`${result.specPath}: ${r.status} (${r.passed}/9)${r.open.length ? `, open: ${r.open.join(", ")}` : ""}`);
|
|
330
|
+
for (const t of r.tests) if (!t.pass) console.log(` ✗ ${t.n}. ${t.name}`);
|
|
331
|
+
for (const f of result.lintFindings) console.log(` ${f.severity === "fail" ? "fail" : "warn"} [${f.test}] ${f.message}\n fix: ${f.fix}`);
|
|
332
|
+
console.log("no stations run: the spec is not ready (run `hyperspec lint` on it for details)");
|
|
333
|
+
}
|
|
334
|
+
process.exit(result.code);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (json) {
|
|
338
|
+
console.log(JSON.stringify(result, null, 2));
|
|
339
|
+
} else {
|
|
340
|
+
for (const s of result.stations) {
|
|
341
|
+
if (s.status === "skip") { console.log(`${s.station}: skip (${s.reason})`); continue; }
|
|
342
|
+
console.log(`${s.station}: ${s.status}`);
|
|
343
|
+
for (const finding of s.findings) console.log(` ${finding.severity === "fail" ? "fail" : "warn"} [${finding.id}] ${finding.message}${typeof finding.line === "number" ? ` (line ${finding.line})` : ""}\n fix: ${finding.fix}`);
|
|
344
|
+
}
|
|
345
|
+
if (result.verdict) {
|
|
346
|
+
const detail = result.verdictDetail.change ?? result.verdictDetail.reason;
|
|
347
|
+
console.log(`verdict: ${result.verdict}${detail ? ` (${detail})` : ""}`);
|
|
348
|
+
}
|
|
349
|
+
if (result.ledgerWarning) console.log(`warn: ${result.ledgerWarning}`);
|
|
350
|
+
}
|
|
351
|
+
process.exit(result.code);
|
|
352
|
+
}
|
|
353
|
+
|
|
179
354
|
// Generic flag/positional parser for the recipe verbs below. A value-taking flag (valueFlags,
|
|
180
355
|
// repeatableFlags) never swallows a following --flag as its value (missing value is an error, not
|
|
181
356
|
// a silent grab); a bool flag never eats the next token as a positional; any --flag not declared
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dna": "0.1",
|
|
3
|
+
"scope": {
|
|
4
|
+
"writer": "example-author",
|
|
5
|
+
"form": "essay",
|
|
6
|
+
"audience": "new managers",
|
|
7
|
+
"purpose": "teach"
|
|
8
|
+
},
|
|
9
|
+
"goldens": [
|
|
10
|
+
{
|
|
11
|
+
"path": "goldens/close.md",
|
|
12
|
+
"sha256": "a90291714fc3bd79a16c613deb536b34c4ef577e56083c0c6b17142fbc4b871f"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"path": "goldens/opening.md",
|
|
16
|
+
"sha256": "395c7c37d604e255a07603276bac0d2f0915bc80d9acea45e3a30a6fe248b44e"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "goldens/status.md",
|
|
20
|
+
"sha256": "891c34d9134380e5c752e69036ec9707729b7e30c9f2b3f2789b8d3ee3f311ed"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"features": {
|
|
24
|
+
"word_count": 118,
|
|
25
|
+
"sentence_length": {
|
|
26
|
+
"mean": 13.111,
|
|
27
|
+
"median": 15,
|
|
28
|
+
"p90": 25
|
|
29
|
+
},
|
|
30
|
+
"paragraph_length": {
|
|
31
|
+
"mean_sentences": 3,
|
|
32
|
+
"mean_words": 39.333
|
|
33
|
+
},
|
|
34
|
+
"rates_per_1000_words": {
|
|
35
|
+
"comma": 33.898,
|
|
36
|
+
"semicolon": 0,
|
|
37
|
+
"colon": 8.475,
|
|
38
|
+
"em_dash": 0,
|
|
39
|
+
"en_dash": 0,
|
|
40
|
+
"exclamation": 0,
|
|
41
|
+
"question_mark": 0,
|
|
42
|
+
"parentheses": 0,
|
|
43
|
+
"quotation_marks": 0
|
|
44
|
+
},
|
|
45
|
+
"contraction_rate": 0,
|
|
46
|
+
"first_person_singular_rate": 0,
|
|
47
|
+
"first_person_plural_rate": 0,
|
|
48
|
+
"second_person_rate": 59.322,
|
|
49
|
+
"mean_word_length": 3.831,
|
|
50
|
+
"signature_words": [
|
|
51
|
+
"first",
|
|
52
|
+
"report",
|
|
53
|
+
"tracker"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Goldens
|
|
2
|
+
|
|
3
|
+
Each file in this folder except this one is a golden: a passage the writer marked as right,
|
|
4
|
+
filed under this scope.
|
|
5
|
+
|
|
6
|
+
Frontmatter:
|
|
7
|
+
- why (required): what makes it golden, the move it teaches.
|
|
8
|
+
- approved_by (required): a person slug. Golden means a human approved it; agent:* is refused.
|
|
9
|
+
- source (required): where the passage came from.
|
|
10
|
+
- approved_on (optional): a date.
|
|
11
|
+
|
|
12
|
+
The body is the passage, verbatim.
|
|
13
|
+
|
|
14
|
+
Run `hyperspec dna measure <scope-dir>` once every golden here has why, approved_by and source.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
why: ends on an instruction and gives the reason for it in the same sentence
|
|
3
|
+
approved_by: example-author
|
|
4
|
+
source: first draft of this essay's closing paragraph, marked golden on the review page
|
|
5
|
+
approved_on: "2026-09-18"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
So write the three questions on a card. Ask the first one. Then wait, longer than feels polite,
|
|
9
|
+
because the first answer is the one they rehearsed and the second one is the one you came for.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
why: one plain claim in the first sentence, then two short sentences that turn it into something to do
|
|
3
|
+
approved_by: example-author
|
|
4
|
+
source: first draft of this essay's opening paragraph, marked golden on the review page
|
|
5
|
+
approved_on: "2026-09-18"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Your first one-on-one with a new report is the only meeting on your calendar where they should
|
|
9
|
+
set the agenda. Everything else you run. This one you hand over.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
why: states what to stop doing, then where that thing already lives, then what the freed half hour is for, one sentence each
|
|
3
|
+
approved_by: example-author
|
|
4
|
+
source: an earlier newsletter essay for new managers by the same writer, its middle section
|
|
5
|
+
approved_on: "2026-09-18"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
If you can read it in the tracker, do not ask for it in the room. Status already has a home,
|
|
9
|
+
and your report is the one who put it there. Spend the half hour on what the tracker cannot
|
|
10
|
+
hold: how the work looks from their side of it.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{"text":"29 of 41 people said their most useful one-on-one in the last quarter was one where they brought the first topic.","source":"survey#s3","span":"29 of 41 said their most useful one-on-one in the last quarter was one where they brought the first topic."}
|
|
2
|
+
{"text":"That is how an engineering manager I interviewed, eight years into the job, runs hers.","source":"interview#s4","span":"She keeps a shared document per person; they add items before the meeting, and she adds hers last, at the bottom."}
|
|
3
|
+
{"text":"Her rule is short: the report owns the agenda.","source":"interview#s3","span":"Her rule: the report owns the agenda."}
|
|
4
|
+
{"text":"My first one-on-one as a manager was a disaster, and the reason was simple: I ran it.","source":"voice-memo#s2","span":"My first one-on-one as a manager was a disaster, and the reason was simple: I ran it."}
|
|
5
|
+
{"text":"11 of 41 said at least one of their one-on-ones in the last quarter was mostly project status.","source":"survey#s4","span":"11 of 41 said at least one of their one-on-ones in the last quarter was mostly project status."}
|
|
6
|
+
{"text":"What is taking more of your energy than it should?","source":"voice-memo#s4","span":"What is taking more of your energy than it should?"}
|
|
7
|
+
{"text":"What do you want to be doing more of in six months?","source":"voice-memo#s4","span":"What do you want to be doing more of in six months?"}
|
|
8
|
+
{"text":"What should I stop doing, or start doing, that would make your week easier?","source":"voice-memo#s4","span":"What should I stop doing, or start doing, that would make your week easier?"}
|
|
9
|
+
{"text":"The most common free-text request in the survey, in 9 responses, was to be asked what they want to work on next.","source":"survey#s5","span":"The most common free-text request, in 9 responses: \"ask me what I want to work on next.\""}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Hand your first one-on-one to the person you manage
|
|
2
|
+
|
|
3
|
+
## Who sets the agenda
|
|
4
|
+
|
|
5
|
+
Your first one-on-one with a new report is the only meeting on your calendar where they should
|
|
6
|
+
set the agenda. Everything else you run. This one you hand over.
|
|
7
|
+
|
|
8
|
+
If you walk in with a list, you have told your report what the meeting is for, and it is for
|
|
9
|
+
you. They will answer your list politely and leave. You will know nothing you did not know when
|
|
10
|
+
you sat down, and so will they.
|
|
11
|
+
|
|
12
|
+
My team ran a [survey](materials/team-survey.md) this spring. 29 of 41 people said their most useful one-on-one in the last
|
|
13
|
+
quarter was one where they brought the first topic. The meeting that worked for them was the one
|
|
14
|
+
they started.
|
|
15
|
+
|
|
16
|
+
So give them the start. The simplest way to do it is a running agenda: one shared document per
|
|
17
|
+
person, kept for as long as you manage them. Your report adds items before each meeting, and you
|
|
18
|
+
add yours last, at the bottom. That is how an engineering manager I interviewed, eight years into
|
|
19
|
+
the job, runs hers. Her rule is short: the report owns the agenda.
|
|
20
|
+
|
|
21
|
+
## My first one-on-one
|
|
22
|
+
|
|
23
|
+
My first one-on-one as a manager was a disaster, and the reason was simple: I ran it. I had a
|
|
24
|
+
list, and I went down the list. Project status, blockers, the thing from Tuesday. Thirty minutes
|
|
25
|
+
later my report said thanks and left, and I had learned nothing I could not have read in the
|
|
26
|
+
tracker.
|
|
27
|
+
|
|
28
|
+
What I ran was a status meeting, which is a meeting spent reading out loud what the tracker
|
|
29
|
+
already says. Your report wrote those updates. Asking them to recite the updates to you teaches
|
|
30
|
+
you nothing new, and it spends the one half hour a week that belongs to them.
|
|
31
|
+
|
|
32
|
+
The survey shows the cost from their side. 11 of 41 said at least one of their one-on-ones in the
|
|
33
|
+
last quarter was mostly project status. Read the tracker before you walk in, and leave status
|
|
34
|
+
there.
|
|
35
|
+
|
|
36
|
+
## The three questions
|
|
37
|
+
|
|
38
|
+
Here is what I ask now, in this order, and then I let the report take over:
|
|
39
|
+
|
|
40
|
+
1. What is taking more of your energy than it should?
|
|
41
|
+
2. What do you want to be doing more of in six months?
|
|
42
|
+
3. What should I stop doing, or start doing, that would make your week easier?
|
|
43
|
+
|
|
44
|
+
Three is enough to hand the meeting over. The first asks about this week. The second asks about
|
|
45
|
+
the next six months. The third asks about you, and it is the one your report will not raise
|
|
46
|
+
without being asked. A fourth question starts to look like your list again, and the list is what
|
|
47
|
+
you came to give up.
|
|
48
|
+
|
|
49
|
+
Ask them in the same words every time. Your report will learn them, and after a few weeks they
|
|
50
|
+
will walk in with answers already half formed. That is the point of fixing the words: the meeting
|
|
51
|
+
starts on their topic before you have said anything at all.
|
|
52
|
+
|
|
53
|
+
The second question is the one my own team asked for. The most common free-text request in the
|
|
54
|
+
survey, in 9 responses, was to be asked what they want to work on next.
|
|
55
|
+
|
|
56
|
+
After the third question, stop talking.
|
|
57
|
+
|
|
58
|
+
## What to do with the answers
|
|
59
|
+
|
|
60
|
+
Wait after each question, longer than you want to. Dana, the engineering manager I interviewed,
|
|
61
|
+
puts it in three short sentences: "Wait. Count to five. The real answer is the second one." The first
|
|
62
|
+
answer your report gives is the tidy one, the version they could give anyone. The second is what
|
|
63
|
+
they came in with, and you only hear it if you let the silence run.
|
|
64
|
+
|
|
65
|
+
Write down what they say, in their words, at the top of the running agenda. That list is where
|
|
66
|
+
your next one-on-one starts, so the meeting stays theirs the week after too.
|
|
67
|
+
|
|
68
|
+
Do not try to fix everything in the room. Pick one thing you can act on this week, say what you
|
|
69
|
+
will do, and do it before you meet again. The rest stays on the running agenda until it is done
|
|
70
|
+
or your report takes it off.
|
|
71
|
+
|
|
72
|
+
And keep your own urgent items out of it. She told me: "If I have something urgent, it is not a
|
|
73
|
+
one-on-one topic. I send it the day it happens." Your report should never have to wait a week to
|
|
74
|
+
hear something you needed them to know on Monday.
|
|
75
|
+
|
|
76
|
+
## Before the meeting
|
|
77
|
+
|
|
78
|
+
Open the invite and delete your list from it. Ask your report to add the first item to the
|
|
79
|
+
running agenda instead.
|
|
80
|
+
|
|
81
|
+
So write the three questions on a card. Ask the first one. Then wait, longer than feels polite,
|
|
82
|
+
because the first answer is the one they rehearsed and the second one is the one you came for.
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
{"id":"s2","start":119,"end":199,"label":"aside","text":"Considered: the manager reviewed these notes afterwards and corrected two\nlines."}
|
|
4
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
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":"
|
|
6
|
+
{"id":"s5","start":357,"end":448,"label":"quote","speaker":"dana, an engineering manager","text":"- \"If I have something urgent, it is not a one-on-one topic. I send it the day it happens.\""}
|
|
7
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
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":"
|
|
9
|
+
{"id":"s8","start":674,"end":733,"label":"quote","speaker":"dana, an engineering manager","text":"\"Wait. Count to five. The real answer is\n the second one.\""}
|
|
10
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."}
|
|
@@ -28,7 +28,7 @@ requirements:
|
|
|
28
28
|
fails_when: a reader shown only the first two sentences cannot say who should set the agenda
|
|
29
29
|
check:
|
|
30
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
|
|
31
|
+
source: dna/essay-new-managers-teach/goldens/opening.md
|
|
32
32
|
author: example-author
|
|
33
33
|
- id: r2
|
|
34
34
|
text: the three questions appear word for word as the voice memo states them
|
|
@@ -71,10 +71,10 @@ rejects:
|
|
|
71
71
|
- any claim about what most managers do that the survey does not support
|
|
72
72
|
- the walking one-on-one aside from the voice memo
|
|
73
73
|
examples:
|
|
74
|
-
- path: essay/goldens/opening.md
|
|
75
|
-
why: the claim lands in the first sentence, and the
|
|
74
|
+
- path: dna/essay-new-managers-teach/goldens/opening.md
|
|
75
|
+
why: the claim lands in the first sentence, and the two short sentences after it turn it into an instruction
|
|
76
76
|
resume:
|
|
77
|
-
next_action:
|
|
77
|
+
next_action: hand essay/draft.md to the doctor for the rubric checks in r1, r4 and r5, now that hyperspec check passes it
|
|
78
78
|
feedback:
|
|
79
79
|
issues: https://github.com/SupersuitUp/hyperspec/issues
|
|
80
80
|
fork: MIT; fork it for your own purposes
|
|
@@ -110,18 +110,21 @@ writing:
|
|
|
110
110
|
author: agent:claude
|
|
111
111
|
dna:
|
|
112
112
|
writer: example-author
|
|
113
|
+
scope_dir: dna/essay-new-managers-teach
|
|
113
114
|
scope:
|
|
114
115
|
form: essay
|
|
115
116
|
audience: new managers
|
|
116
117
|
purpose: teach
|
|
117
118
|
rules: style-rules.md
|
|
118
119
|
goldens:
|
|
119
|
-
- path: essay/goldens/opening.md
|
|
120
|
-
why: one plain claim, then
|
|
121
|
-
- path: essay/goldens/
|
|
120
|
+
- path: dna/essay-new-managers-teach/goldens/opening.md
|
|
121
|
+
why: one plain claim in the first sentence, then two short sentences that turn it into something to do
|
|
122
|
+
- path: dna/essay-new-managers-teach/goldens/status.md
|
|
123
|
+
why: states what to stop doing, then where that thing already lives, then what the freed half hour is for, one sentence each
|
|
124
|
+
- path: dna/essay-new-managers-teach/goldens/close.md
|
|
122
125
|
why: ends on an instruction and gives the reason for it in the same sentence
|
|
123
126
|
check:
|
|
124
|
-
rubric: blind lineup within this scope; a judge shown
|
|
127
|
+
rubric: blind lineup within this scope; a judge shown a generated passage beside the scope's three goldens cannot pick it out
|
|
125
128
|
source: goldens marked on the review page
|
|
126
129
|
author: example-author
|
|
127
130
|
persona:
|
|
@@ -145,6 +148,9 @@ writing:
|
|
|
145
148
|
- one-on-one
|
|
146
149
|
- report
|
|
147
150
|
- tracker
|
|
151
|
+
terms:
|
|
152
|
+
- running agenda
|
|
153
|
+
- status meeting
|
|
148
154
|
believes_now: a one-on-one is where a manager catches up on how the work is going
|
|
149
155
|
wants: a plan for the first meeting that will not waste either person's half hour
|
|
150
156
|
reads_on: a phone, in the ten minutes before the meeting
|
|
@@ -173,11 +179,11 @@ writing:
|
|
|
173
179
|
max: 1100
|
|
174
180
|
unit: words
|
|
175
181
|
required_parts:
|
|
176
|
-
-
|
|
177
|
-
-
|
|
182
|
+
- who sets the agenda
|
|
183
|
+
- my first one-on-one
|
|
178
184
|
- the three questions
|
|
179
185
|
- what to do with the answers
|
|
180
|
-
-
|
|
186
|
+
- before the meeting
|
|
181
187
|
stations:
|
|
182
188
|
- the three questions render as a numbered list
|
|
183
189
|
check:
|
|
@@ -216,5 +222,18 @@ fiction: false
|
|
|
216
222
|
# Hand your first one-on-one to the person you manage
|
|
217
223
|
|
|
218
224
|
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/
|
|
220
|
-
|
|
225
|
+
it is drafted. Every file this spec names ships beside it. The draft is `essay/draft.md`, and
|
|
226
|
+
the claims ledger written while drafting it is `essay/claims.jsonl`. The draft passes every
|
|
227
|
+
deterministic station:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
npx @supersuit/hyperspec check essay.hyperspec.md --draft essay/draft.md
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The required parts are the draft's section headings, because the form station finds a required
|
|
234
|
+
part by its heading.
|
|
235
|
+
|
|
236
|
+
The writer's voice for this piece comes from a scope folder, `dna/essay-new-managers-teach/`:
|
|
237
|
+
the goldens this writer approved for essays that teach new managers, and the features measured
|
|
238
|
+
from them. Another essay by the same writer, for the same readers and the same purpose, would
|
|
239
|
+
point at the same folder.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{"text":"Flour is weighed at Ines's, never scooped.","source":"bakery-visit#s10","span":"Flour is weighed, never scooped."}
|
|
2
|
+
{"text":"The water temperature is checked every batch at Ines's","source":"bakery-visit#s11","span":"Water temperature is checked every batch."}
|
|
3
|
+
{"text":"Ines does not talk while she shapes.","source":"bakery-visit#s8","span":"The owner does not talk while shaping."}
|
|
4
|
+
{"text":"Talking happens at the mixer and at the till","source":"bakery-visit#s9","span":"Talking happens at the mixer and at the till."}
|
|
5
|
+
{"text":"In winter the rye proofs about three hours at room temperature.","source":"bakery-visit#s4","span":"Rye sourdough proofs about three hours at room temperature in winter."}
|
|
6
|
+
{"text":"The back left of the deck oven runs hot, so every tray is turned halfway through the bake.","source":"bakery-visit#s5","span":"Trays are turned halfway through the bake because the back left of a deck oven runs hot."}
|
|
7
|
+
{"text":"The doors open to customers at 7:00.","source":"bakery-visit#s6","span":"The doors open to customers at 7:00."}
|
|
8
|
+
{"text":"The first person in is usually a regular.","source":"bakery-visit#s7","span":"The first person in is usually a regular."}
|
|
9
|
+
{"text":"turning those trays with her since I was sixteen","source":"notes#s2","span":"the apprentice she took on at sixteen, now nineteen"}
|