polymath-society 0.2.4

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 (45) hide show
  1. package/LICENSE +52 -0
  2. package/README.md +311 -0
  3. package/dist/DATA-MAP.md +109 -0
  4. package/dist/cli.js +24136 -0
  5. package/dist/engine/closing-rubric.md +50 -0
  6. package/dist/engine/exp-allfacets.js +16832 -0
  7. package/dist/engine/exp-person.js +16922 -0
  8. package/dist/engine/exp-pipeline.js +16700 -0
  9. package/dist/engine/feel-seen-rubric.md +192 -0
  10. package/dist/engine/ingest-export.js +1423 -0
  11. package/dist/engine/peak-demos.js +16752 -0
  12. package/dist/engine/person-dimension-summary.js +16744 -0
  13. package/dist/engine/person-facet-lines.js +16784 -0
  14. package/dist/engine/person-headline.js +16733 -0
  15. package/dist/engine/person-report.js +16845 -0
  16. package/dist/engine/person-self-image.js +16689 -0
  17. package/dist/engine/public-report-guidelines.md +80 -0
  18. package/dist/engine/public-report.js +17284 -0
  19. package/dist/engine/run-analysis.js +16035 -0
  20. package/dist/engine/run-tagger.js +16092 -0
  21. package/dist/engine/top-companies.md +41 -0
  22. package/dist/engine/writing-well.md +56 -0
  23. package/dist/index.js +22021 -0
  24. package/dist/pipeline/TECHNIQUES.md +406 -0
  25. package/dist/pipeline/WRITING.md +48 -0
  26. package/dist/pipeline/coding-agglomerate.js +15876 -0
  27. package/dist/pipeline/coding-aggregate.js +440 -0
  28. package/dist/pipeline/coding-build.js +1168 -0
  29. package/dist/pipeline/coding-coaching.js +16893 -0
  30. package/dist/pipeline/coding-day-digest.js +15869 -0
  31. package/dist/pipeline/coding-delegation.js +16292 -0
  32. package/dist/pipeline/coding-expertise.js +15925 -0
  33. package/dist/pipeline/coding-flow.js +313 -0
  34. package/dist/pipeline/coding-frontier-detail.js +15878 -0
  35. package/dist/pipeline/coding-frontier.js +51 -0
  36. package/dist/pipeline/coding-gap-dist.js +293 -0
  37. package/dist/pipeline/coding-gap.js +17108 -0
  38. package/dist/pipeline/coding-grade.js +16195 -0
  39. package/dist/pipeline/coding-nutshell.js +15725 -0
  40. package/dist/pipeline/coding-projects.js +104 -0
  41. package/dist/pipeline/coding-walkthrough.js +15947 -0
  42. package/dist/web/app.js +12024 -0
  43. package/dist/web/index.html +1 -0
  44. package/dist/web/styles.css +1 -0
  45. package/package.json +61 -0
@@ -0,0 +1,440 @@
1
+ import{createRequire as __cr}from'module';const require=__cr(import.meta.url);
2
+
3
+ // ../../scripts/coding-aggregate.mts
4
+ import { promises as fs2 } from "fs";
5
+ import path from "path";
6
+
7
+ // ../../lib/agents/shared/cliAdapter.ts
8
+ var CLAUDE_BIN = process.env.CLAUDE_BIN || "claude";
9
+
10
+ // ../../lib/agents/shared/backends.ts
11
+ import os from "os";
12
+ var HOME = os.homedir();
13
+
14
+ // ../../lib/agents/shared/tools.ts
15
+ import { execFile } from "child_process";
16
+ import { promisify } from "util";
17
+ var exec = promisify(execFile);
18
+
19
+ // ../../lib/agents/shared/limitGate.ts
20
+ import { AsyncLocalStorage } from "async_hooks";
21
+ var MIN = 6e4;
22
+ var HOUR = 60 * MIN;
23
+ var STALL_BACKOFFS_MS = [6e4, 3 * MIN, 10 * MIN, 20 * MIN];
24
+ var SLEEP_CHUNK_MS = 5 * MIN;
25
+ var CANONICAL = [
26
+ /claude (ai )?usage limit reached/i,
27
+ /\busage limit reached\b/i,
28
+ // "session" variant seen live 2026-07-06: "You've hit your session limit ·
29
+ // resets 4:10am (America/Los_Angeles)" — unrecognized, it failed EVERY
30
+ // post-wall stage of the overnight rehearsal instead of pausing. Keep the
31
+ // subject alternatives in sync with what the CLI actually emits.
32
+ /you'?ve (?:hit|reached) your (?:usage |session |weekly |daily )?limit/i,
33
+ /\bsession limit\b[^.]{0,40}resets?/i,
34
+ /reached your usage limit/i,
35
+ /5[\s-]?hour limit (?:reached|hit)/i,
36
+ /limit will reset/i
37
+ ];
38
+ var LIBERAL = [
39
+ ...CANONICAL,
40
+ /usage limit/i,
41
+ /\bsession limit\b/i,
42
+ /\brate[\s-]?limit(?:ed|s|\b)/i,
43
+ /\btoo many requests\b/i,
44
+ /\b429\b/,
45
+ /quota (?:exceeded|reached|exhausted)/i,
46
+ /resets? (?:at|in)\b/i,
47
+ /resets? \d{1,2}(?::\d{2})?\s*(?:am|pm)/i
48
+ // "resets 4:10am" — no "at" (live form)
49
+ ];
50
+ var ALS = new AsyncLocalStorage();
51
+
52
+ // ../coding-core/dist/messages.js
53
+ import { promises as fs } from "fs";
54
+
55
+ // ../coding-core/dist/injected.js
56
+ var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
57
+ var INJECTED_RES = [
58
+ /<task-notification>[\s\S]*?<\/task-notification>/g,
59
+ /<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g,
60
+ /<local-command-caveat>[\s\S]*?<\/local-command-caveat>/g,
61
+ /<ide_opened_file>[\s\S]*?<\/ide_opened_file>/g
62
+ ];
63
+ var HARNESS_TURN_RES = [
64
+ /^Non user activity$/i,
65
+ /^<<autonomous-loop(-dynamic)?>>$/,
66
+ /^This session is being continued from a previous conversation/,
67
+ // the loop skill's expanded wake-up prompt — older CLI versions wrote it as a
68
+ // plain user turn with no isMeta flag (found counting 601 words, 2026-07-07)
69
+ /^(-{3,}\s*)?#\s*Autonomous loop (check|tick)/
70
+ ];
71
+ function stripInjected(t) {
72
+ for (const re of INJECTED_RES)
73
+ t = t.replace(re, "");
74
+ return t.replace(SYSTEM_REMINDER_RE, "");
75
+ }
76
+ function isHarnessTurn(t) {
77
+ const s = t.trim();
78
+ return HARNESS_TURN_RES.some((re) => re.test(s));
79
+ }
80
+ function humanTypedText(t) {
81
+ const s = stripInjected(t).trim();
82
+ return s && !isHarnessTurn(s) ? s : "";
83
+ }
84
+ function isHarnessEntry(e) {
85
+ return e.isMeta === true || e.isCompactSummary === true;
86
+ }
87
+
88
+ // ../coding-core/dist/messages.js
89
+ function extractText(content) {
90
+ if (typeof content === "string")
91
+ return content;
92
+ if (Array.isArray(content))
93
+ return content.filter((c) => c && typeof c === "object" && c.type === "text").map((c) => c.text || "").join("\n");
94
+ return "";
95
+ }
96
+ function isToolResultOnly(content) {
97
+ return Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
98
+ }
99
+ function createMessageDeduper() {
100
+ const seen = /* @__PURE__ */ new Set();
101
+ return (m) => {
102
+ const key = m.uuid ?? `${m.t}|${m.text}`;
103
+ if (seen.has(key))
104
+ return false;
105
+ seen.add(key);
106
+ return true;
107
+ };
108
+ }
109
+ async function extractMessages(file) {
110
+ let raw;
111
+ try {
112
+ raw = await fs.readFile(file, "utf-8");
113
+ } catch {
114
+ return [];
115
+ }
116
+ const out = [];
117
+ const userTexts = /* @__PURE__ */ new Set();
118
+ const seenUuids = /* @__PURE__ */ new Set();
119
+ let aiProse = [];
120
+ let aiTools = {};
121
+ let aiStart = 0;
122
+ const flushAI = () => {
123
+ if (!aiProse.length && Object.keys(aiTools).length === 0)
124
+ return;
125
+ const toolStr = Object.entries(aiTools).map(([n, c]) => c > 1 ? `${n}\xD7${c}` : n).join(", ");
126
+ let prose = aiProse.join(" ").replace(/\s+/g, " ").trim();
127
+ if (prose.length > 2200)
128
+ prose = prose.slice(0, 2200) + "\u2026";
129
+ let text = prose;
130
+ if (toolStr)
131
+ text = (text ? text + " " : "") + `[ran ${toolStr}]`;
132
+ if (text)
133
+ out.push({ t: aiStart, role: "ai", text });
134
+ aiProse = [];
135
+ aiTools = {};
136
+ };
137
+ for (const line of raw.split("\n")) {
138
+ const s = line.trim();
139
+ if (!s)
140
+ continue;
141
+ let e;
142
+ try {
143
+ e = JSON.parse(s);
144
+ } catch {
145
+ continue;
146
+ }
147
+ const t = typeof e.timestamp === "string" ? Date.parse(e.timestamp) : NaN;
148
+ if (!Number.isFinite(t))
149
+ continue;
150
+ if (e.type === "queue-operation" && e.operation === "enqueue" && typeof e.content === "string") {
151
+ const qt = humanTypedText(e.content);
152
+ if (qt) {
153
+ flushAI();
154
+ out.push({ t, role: "user", text: qt, q: true });
155
+ }
156
+ continue;
157
+ }
158
+ if (e.type === "user") {
159
+ if (isHarnessEntry(e))
160
+ continue;
161
+ const uuid = typeof e.uuid === "string" ? e.uuid : "";
162
+ if (uuid && seenUuids.has(uuid))
163
+ continue;
164
+ const content = e.message?.content;
165
+ if (isToolResultOnly(content))
166
+ continue;
167
+ const text = humanTypedText(extractText(content));
168
+ if (text) {
169
+ if (uuid)
170
+ seenUuids.add(uuid);
171
+ userTexts.add(text);
172
+ flushAI();
173
+ out.push({ t, role: "user", text });
174
+ }
175
+ continue;
176
+ }
177
+ if (e.type === "assistant") {
178
+ const msg = e.message ?? {};
179
+ if (!aiProse.length && Object.keys(aiTools).length === 0)
180
+ aiStart = t;
181
+ if (Array.isArray(msg.content)) {
182
+ for (const item of msg.content) {
183
+ const it = item;
184
+ if (it.type === "text" && it.text)
185
+ aiProse.push(it.text);
186
+ else if (it.type === "tool_use" && it.name)
187
+ aiTools[it.name] = (aiTools[it.name] ?? 0) + 1;
188
+ }
189
+ } else {
190
+ const p = extractText(msg.content);
191
+ if (p)
192
+ aiProse.push(p);
193
+ }
194
+ }
195
+ }
196
+ flushAI();
197
+ return out.filter((m) => !(m.q && userTexts.has(m.text))).map(({ q, ...m }) => m).sort((a, b) => a.t - b.t);
198
+ }
199
+
200
+ // ../coding-core/dist/words.js
201
+ var CODE_START = /^\s*(import |export |const |let |var |function\b|class |def |async |await |return |if\s*\(|for\s*\(|while\s*\(|switch\s*\(|#include|<\/?[a-zA-Z][\w-]*[\s>]|[}{)\]]|@[A-Za-z]|\$\s|npm |npx |git |cd |sudo )/;
202
+ var SEAM_RE = /\n\s*\n\s*\n+/;
203
+ var SEGMENT_CEILING = 400;
204
+ var NOTE = "((ran|created|edited|read|wrote|searched) (a|an|\\d+) [a-z]+|updated todos)";
205
+ var TRANSCRIPT_LINE_RE = new RegExp(`^\\s*(${NOTE})((,| and) ${NOTE})*\\s*$|^\\s*thought for .{1,30}$`, "im");
206
+ var EM_DASH_RE = / — /g;
207
+ var MAX_WPM = 1e3;
208
+ var DEFAULT_GAP_MIN = 5;
209
+ function typedWords(text, gapMs) {
210
+ const typed = humanTypedText(text);
211
+ const msgDashes = (typed.match(EM_DASH_RE) || []).length;
212
+ const msgWords = proseWords(typed);
213
+ const draftedMsg = msgDashes >= 6 && msgWords > 0 && msgDashes / msgWords >= 1 / 200;
214
+ let counted = 0;
215
+ for (const seg of typed.split(SEAM_RE)) {
216
+ if (TRANSCRIPT_LINE_RE.test(seg))
217
+ continue;
218
+ const w = proseWords(seg);
219
+ if (!w)
220
+ continue;
221
+ const dashes = (seg.match(EM_DASH_RE) || []).length;
222
+ if (dashes >= 3 && dashes / w >= 1 / 200)
223
+ continue;
224
+ if (draftedMsg && dashes >= 1)
225
+ continue;
226
+ counted += Math.min(w, SEGMENT_CEILING);
227
+ }
228
+ const mins = gapMs / 6e4;
229
+ const window = Number.isFinite(mins) && mins > 0 ? mins : DEFAULT_GAP_MIN;
230
+ return Math.min(counted, Math.round(MAX_WPM * window));
231
+ }
232
+ function proseWords(text) {
233
+ let t = text;
234
+ const skillAt = t.indexOf("Base directory for this skill:");
235
+ if (skillAt >= 0)
236
+ t = t.slice(0, skillAt);
237
+ const contAt = t.indexOf("This session is being continued from a previous conversation");
238
+ if (contAt >= 0)
239
+ t = t.slice(0, contAt);
240
+ t = t.replace(/<command-[a-z-]+>[\s\S]*?<\/command-[a-z-]+>/gi, " ");
241
+ t = t.replace(/```[\s\S]*?```/g, " ");
242
+ t = t.replace(/`[^`\n]{2,}`/g, " ");
243
+ const kept = t.split("\n").filter((ln) => {
244
+ const s = ln.trim();
245
+ if (s.length < 8)
246
+ return true;
247
+ const letters = (s.match(/[A-Za-z]/g) || []).length;
248
+ const symbols = (s.match(/[{}()[\];=<>+\-*/%&|^~$@\\]/g) || []).length;
249
+ const codey = CODE_START.test(s) || symbols > 3 && symbols / s.length > 0.25 || // symbol-dense → code
250
+ letters / s.length < 0.45;
251
+ return !codey;
252
+ });
253
+ return (kept.join(" ").trim().match(/\S+/g) || []).length;
254
+ }
255
+
256
+ // ../coding-core/dist/throughput.js
257
+ var THROUGHPUT_LADDER = [
258
+ { score: 11, min: 12e3, label: "superhuman \u2014 a day beyond the proven ceiling (unreached target)" },
259
+ { score: 10, min: 9e3, label: "world-class \u2014 sustained, very high directed output" },
260
+ { score: 9, min: 7e3, label: "exceptional output day" },
261
+ { score: 8, min: 6e3, label: "high-throughput day" },
262
+ { score: 7, min: 4500, label: "strong working day" },
263
+ { score: 6, min: 3e3, label: "solid focused day" },
264
+ { score: 5, min: 2e3, label: "real but moderate" },
265
+ { score: 4, min: 1200, label: "light" },
266
+ { score: 3, min: 600, label: "brief" },
267
+ { score: 2, min: 200, label: "incidental" },
268
+ { score: 1, min: 0, label: "barely touched it" }
269
+ ];
270
+ function throughputScore(words) {
271
+ for (const r of THROUGHPUT_LADDER)
272
+ if (words >= r.min)
273
+ return r.score;
274
+ return 1;
275
+ }
276
+ function throughputCeiling(dayWords, proof = 3) {
277
+ const scores = dayWords.map(throughputScore).sort((a, b) => b - a);
278
+ if (scores.length === 0)
279
+ return 0;
280
+ return scores[Math.min(proof, scores.length) - 1];
281
+ }
282
+
283
+ // ../../scripts/coding-aggregate.mts
284
+ var CODING = ".data/coding";
285
+ var TZ = "America/Los_Angeles";
286
+ var readJson = async (f, d) => {
287
+ try {
288
+ return JSON.parse(await fs2.readFile(path.join(CODING, f), "utf8"));
289
+ } catch {
290
+ return d;
291
+ }
292
+ };
293
+ async function throughputAvg(spanHourMin) {
294
+ const flow = await readJson("flow.json", { days: [] });
295
+ const spanH = {};
296
+ for (const d of flow.days || []) {
297
+ const slots = Object.keys(d.slots || {}).map(Number);
298
+ spanH[d.date] = slots.length ? (Math.max(...slots) - Math.min(...slots) + 1) / 6 : 0;
299
+ }
300
+ const sess = JSON.parse(await fs2.readFile(path.join(CODING, "sessions.json"), "utf8")).filter((s) => !s.duplicateOf && s.klass === "interactive");
301
+ const wordsByDay = {};
302
+ const firstSeen = createMessageDeduper();
303
+ for (const s of sess) {
304
+ let ms = [];
305
+ try {
306
+ ms = await extractMessages(s.file);
307
+ } catch {
308
+ continue;
309
+ }
310
+ for (const m of ms) if (m.role === "user" && firstSeen(m)) {
311
+ const date = new Date(m.t).toLocaleDateString("en-CA", { timeZone: TZ });
312
+ wordsByDay[date] = (wordsByDay[date] || 0) + typedWords(m.text || "", NaN);
313
+ }
314
+ }
315
+ const dates = Object.keys(wordsByDay).filter((d) => wordsByDay[d] > 0);
316
+ const mean = (a) => a.length ? Math.round(a.reduce((x, y) => x + y, 0) / a.length) : 0;
317
+ const substantial = dates.filter((d) => (spanH[d] || 0) >= spanHourMin);
318
+ const avgWords = mean(substantial.map((d) => wordsByDay[d]));
319
+ const sens = {};
320
+ for (const h of [2, 3, 4, 5, 6]) {
321
+ const ds = dates.filter((d) => (spanH[d] || 0) >= h);
322
+ const aw = mean(ds.map((d) => wordsByDay[d]));
323
+ sens["span\u2265" + h + "h"] = { days: ds.length, avgWords: aw, score: throughputScore(aw) };
324
+ }
325
+ const score = throughputScore(avgWords);
326
+ const label = THROUGHPUT_LADDER.find((r) => r.score === score)?.label ?? "";
327
+ return { spanHourMin, substantialDays: substantial.length, totalCodingDays: dates.length, avgWordsPerDay: avgWords, score, label, ceiling: throughputCeiling(dates.map((d) => wordsByDay[d])), daySensitivity: sens };
328
+ }
329
+ async function abilityCeiling() {
330
+ const dir = path.join(CODING, "grades");
331
+ const files = (await fs2.readdir(dir)).filter((f) => f.endsWith(".json"));
332
+ const collect = (key) => [];
333
+ const byKey = { generative: [], taste: [] };
334
+ for (const f of files) {
335
+ let j;
336
+ try {
337
+ j = JSON.parse(await fs2.readFile(path.join(dir, f), "utf8"));
338
+ } catch {
339
+ continue;
340
+ }
341
+ for (const key of ["generative", "taste"]) {
342
+ const c = (j.criteria || []).find((x) => x.key === key);
343
+ if (c && typeof c.score === "number") byKey[key].push({ date: (j.date || "").slice(0, 10), title: (j.title || "").slice(0, 48), score: c.score, instance: (c.instance || "").replace(/\s+/g, " ").slice(0, 600), quote: ((c.quotes || [])[0] || "").replace(/\s+/g, " ").slice(0, 400) });
344
+ }
345
+ }
346
+ const score1 = (rows, label) => {
347
+ const sorted = [...rows].sort((a, b) => b.score - a.score);
348
+ if (!sorted.length) return { label, score: null, peak: null, qualifying: 0, instances: [], consistency: "none", note: "no evidence yet" };
349
+ const peak = sorted[0].score;
350
+ const near = sorted.filter((r) => r.score >= Math.max(8, peak - 1));
351
+ const n = near.length;
352
+ const consistency = n >= 3 ? "consistent" : n === 2 ? "shown twice" : "single peak (thin)";
353
+ const score = n >= 2 ? peak : peak;
354
+ return { label, score, peak, qualifying: n, thin: n < 2, consistency, instances: near.slice(0, 10) };
355
+ };
356
+ return { abstraction: score1(byKey.generative, "Abstraction"), taste: score1(byKey.taste, "Taste") };
357
+ }
358
+ var slotHour = (slot) => Math.floor(slot / 6);
359
+ var hm = (slot) => {
360
+ const h = Math.floor(slot / 6), m = slot % 6 * 10;
361
+ const ap = h < 12 ? "am" : "pm";
362
+ const h12 = h % 12 || 12;
363
+ return `${h12}:${String(m).padStart(2, "0")}${ap}`;
364
+ };
365
+ async function flowStats() {
366
+ const flow = await readJson("flow.json", { days: [], totals: {} });
367
+ const conc = await readJson("concurrency.json", { maxConcurrency: 0, perDay: {} });
368
+ const days = flow.days || [];
369
+ const starts = [], ends = [];
370
+ for (const d of days) {
371
+ const slots = Object.keys(d.slots || {}).map(Number);
372
+ if (!slots.length) continue;
373
+ starts.push(Math.min(...slots));
374
+ ends.push(Math.max(...slots));
375
+ }
376
+ const med = (a) => a.length ? [...a].sort((x, y) => x - y)[Math.floor(a.length / 2)] : 0;
377
+ const hour = Array.from({ length: 24 }, (_, h) => ({ hour: h, F: 0, A: 0 }));
378
+ for (const d of days) for (const [slot, v] of Object.entries(d.slots || {})) {
379
+ const h = slotHour(Number(slot));
380
+ if (v === "F") hour[h].F++;
381
+ else hour[h].A++;
382
+ }
383
+ const flowByHour = hour.map((h) => ({ hour: h.hour, flowSlots: h.F, activeSlots: h.F + h.A, frac: h.F + h.A ? Math.round(h.F / (h.F + h.A) * 100) / 100 : 0 })).filter((h) => h.activeSlots > 0);
384
+ let bestStart = 0, bestSum = -1;
385
+ for (let h = 0; h <= 21; h++) {
386
+ const s = (hour[h]?.F || 0) + (hour[h + 1]?.F || 0) + (hour[h + 2]?.F || 0);
387
+ if (s > bestSum) {
388
+ bestSum = s;
389
+ bestStart = h;
390
+ }
391
+ }
392
+ const peakWindow = bestSum > 0 ? `${hm(bestStart * 6)}\u2013${hm((bestStart + 3) * 6)}` : null;
393
+ const perDay = conc.perDay || {};
394
+ const concOf = (date) => {
395
+ const v = perDay[date];
396
+ return typeof v === "number" ? v : v?.peak ?? v?.max ?? v?.maxConcurrency ?? 1;
397
+ };
398
+ const tiers = { "solo (\xD71)": { fracs: [] }, "light (\xD72\u20133)": { fracs: [] }, "heavy (\xD74+)": { fracs: [] } };
399
+ for (const d of days) {
400
+ const c = concOf(d.date);
401
+ const t = c <= 1 ? "solo (\xD71)" : c <= 3 ? "light (\xD72\u20133)" : "heavy (\xD74+)";
402
+ tiers[t].fracs.push(d.flowFraction || 0);
403
+ }
404
+ const byParallelism = Object.entries(tiers).map(([tier, { fracs }]) => ({ tier, days: fracs.length, avgFlowFraction: fracs.length ? Math.round(fracs.reduce((a, b) => a + b, 0) / fracs.length * 100) / 100 : 0 })).filter((t) => t.days > 0);
405
+ const totalFlowHours = Math.round(days.reduce((a, d) => a + (d.flowHours || 0), 0) * 10) / 10;
406
+ const flowHoursPerDay = days.length ? Math.round(totalFlowHours / days.length * 10) / 10 : 0;
407
+ const wdSlots = starts.length && ends.length ? Math.max(0, med(ends) - med(starts)) : 0;
408
+ const workDayHours = Math.round(wdSlots / 6 * 10) / 10;
409
+ const pctOfWorkDay = workDayHours > 0 ? Math.round(flowHoursPerDay / workDayHours * 100) / 100 : null;
410
+ const bestTier = [...byParallelism].sort((a, b) => b.avgFlowFraction - a.avgFlowFraction)[0];
411
+ const insight = peakWindow ? `Flow peaks ${peakWindow}; deepest on ${bestTier ? bestTier.tier : "\u2014"} days (${bestTier ? Math.round(bestTier.avgFlowFraction * 100) : 0}% in flow).` : "Not enough flow data yet.";
412
+ return {
413
+ workWindow: { typicalStart: starts.length ? hm(med(starts)) : null, typicalEnd: ends.length ? hm(med(ends)) : null, activeDays: days.length },
414
+ parallelism: { maxConcurrent: conc.maxConcurrency || 0 },
415
+ flow: { fractionOfActiveTime: flow.totals?.flowFraction ?? null, pctOfWorkDay, workDayHours, flowHoursPerDay, totalFlowHours },
416
+ peakWindow,
417
+ flowByHour,
418
+ byParallelism,
419
+ insight
420
+ };
421
+ }
422
+ async function main() {
423
+ const ability = await abilityCeiling();
424
+ const flow = await flowStats();
425
+ const throughput = await throughputAvg(4);
426
+ const prev = await readJson("aggregate.json", {});
427
+ const out = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ability, flow, throughput, ...prev.bigPicture ? { bigPicture: prev.bigPicture } : {} };
428
+ await fs2.writeFile(path.join(CODING, "aggregate.json"), JSON.stringify(out, null, 2));
429
+ console.log("aggregate.json written" + (prev.bigPicture ? " (bigPicture preserved)" : " (no bigPicture present \u2014 run coding-nutshell.mts)"));
430
+ console.log(` Throughput AVG (start\u2192finish span \u2265${throughput.spanHourMin}h): ${throughput.score}/11 (${throughput.label}) \xB7 ${throughput.avgWordsPerDay} words/day over ${throughput.substantialDays}/${throughput.totalCodingDays} days \xB7 [peak ceiling was ${throughput.ceiling}]`);
431
+ console.log(` day sensitivity:`, throughput.daySensitivity);
432
+ console.log(` Abstraction ceiling: ${ability.abstraction.score} (${ability.abstraction.consistency}, ${ability.abstraction.qualifying} qualifying)`);
433
+ console.log(` Taste ceiling: ${ability.taste.score} (${ability.taste.consistency}, ${ability.taste.qualifying} qualifying)`);
434
+ console.log(` Work window: ${flow.workWindow.typicalStart}\u2013${flow.workWindow.typicalEnd} \xB7 ${flow.flow.fractionOfActiveTime != null ? Math.round(flow.flow.fractionOfActiveTime * 100) : "?"}% in flow`);
435
+ console.log(` ${flow.insight}`);
436
+ }
437
+ main().catch((e) => {
438
+ console.error(e);
439
+ process.exit(1);
440
+ });