polymath-society 0.2.19 → 0.2.21

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 (40) hide show
  1. package/dist/cli.js +24620 -22073
  2. package/dist/engine/chat-loops.js +8 -1
  3. package/dist/engine/exp-allfacets.js +8 -1
  4. package/dist/engine/exp-person.js +8 -1
  5. package/dist/engine/exp-pipeline.js +8 -1
  6. package/dist/engine/ingest-export.js +7 -6
  7. package/dist/engine/peak-demos.js +8 -1
  8. package/dist/engine/person-dimension-summary.js +8 -1
  9. package/dist/engine/person-facet-lines.js +8 -1
  10. package/dist/engine/person-headline.js +8 -1
  11. package/dist/engine/person-report.js +8 -1
  12. package/dist/engine/person-self-image.js +8 -1
  13. package/dist/engine/public-report.js +15 -7
  14. package/dist/engine/run-analysis.js +8 -1
  15. package/dist/engine/run-tagger.js +8 -1
  16. package/dist/index.js +19600 -17706
  17. package/dist/pipeline/FOCUS-EXAMPLES.md +127 -0
  18. package/dist/pipeline/FOCUS-RUBRIC.md +418 -0
  19. package/dist/pipeline/coding-agglomerate.js +947 -79
  20. package/dist/pipeline/coding-aggregate.js +443 -23
  21. package/dist/pipeline/coding-build.js +569 -97
  22. package/dist/pipeline/coding-coaching.js +651 -101
  23. package/dist/pipeline/coding-day-digest.js +417 -36
  24. package/dist/pipeline/coding-delegation.js +564 -68
  25. package/dist/pipeline/coding-expertise.js +477 -59
  26. package/dist/pipeline/coding-flow.js +424 -15
  27. package/dist/pipeline/coding-focus.js +507 -51
  28. package/dist/pipeline/coding-frontier-detail.js +465 -47
  29. package/dist/pipeline/coding-frontier.js +7 -6
  30. package/dist/pipeline/coding-gap-dist.js +424 -15
  31. package/dist/pipeline/coding-gap.js +679 -128
  32. package/dist/pipeline/coding-grade.js +469 -42
  33. package/dist/pipeline/coding-growth.js +17041 -0
  34. package/dist/pipeline/coding-nutshell.js +168 -151
  35. package/dist/pipeline/coding-projects.js +7 -6
  36. package/dist/pipeline/coding-walkthrough.js +461 -37
  37. package/dist/pipeline/coding-workbrief.js +16387 -0
  38. package/dist/web/app.js +2095 -1396
  39. package/dist/web/styles.css +1 -1
  40. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  import{createRequire as __cr}from'module';const require=__cr(import.meta.url);
2
2
 
3
3
  // ../../scripts/coding-aggregate.mts
4
- import { promises as fs2 } from "fs";
5
- import path2 from "path";
4
+ import { promises as fs3 } from "fs";
5
+ import path3 from "path";
6
6
 
7
7
  // ../../lib/agents/shared/cliAdapter.ts
8
8
  var CLAUDE_BIN = process.env.CLAUDE_BIN || "claude";
@@ -50,7 +50,7 @@ var LIBERAL = [
50
50
  var ALS = new AsyncLocalStorage();
51
51
 
52
52
  // ../coding-core/dist/messages.js
53
- import { promises as fs } from "fs";
53
+ import { promises as fs2 } from "fs";
54
54
 
55
55
  // ../coding-core/dist/injected.js
56
56
  var SYSTEM_REMINDER_RE = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
@@ -85,6 +85,365 @@ function isHarnessEntry(e) {
85
85
  return e.isMeta === true || e.isCompactSummary === true;
86
86
  }
87
87
 
88
+ // ../coding-core/dist/codex.js
89
+ var SYSTEM_REMINDER_RE2 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
90
+ var CODEX_INJECTED_TEXT_RE = /^\s*(<(environment_context|user_instructions|ENVIRONMENT_CONTEXT|turn_aborted)\b|#\s*AGENTS\.md instructions\b)/;
91
+ function isCodexInjectedUserText(text) {
92
+ return CODEX_INJECTED_TEXT_RE.test(text);
93
+ }
94
+ function codexContentText(content) {
95
+ if (typeof content === "string")
96
+ return content;
97
+ if (!Array.isArray(content))
98
+ return "";
99
+ const parts = [];
100
+ for (const item of content) {
101
+ if (typeof item === "string") {
102
+ parts.push(item);
103
+ continue;
104
+ }
105
+ if (item && typeof item === "object") {
106
+ const it = item;
107
+ if (it.type === "input_text" || it.type === "output_text" || it.type === "text")
108
+ parts.push(it.text || "");
109
+ }
110
+ }
111
+ return parts.join("\n");
112
+ }
113
+ function sniffCodexRaw(raw) {
114
+ let checked = 0;
115
+ for (const lineRaw of raw.split("\n")) {
116
+ const line = lineRaw.trim();
117
+ if (!line)
118
+ continue;
119
+ if (checked++ >= 5)
120
+ break;
121
+ try {
122
+ const e = JSON.parse(line);
123
+ if (e.type === "session_meta" || e.type === "response_item" || e.type === "turn_context")
124
+ return true;
125
+ if (e.type === "user" || e.type === "assistant" || e.type === "summary" || e.type === "queue-operation")
126
+ return false;
127
+ } catch {
128
+ }
129
+ }
130
+ return false;
131
+ }
132
+ var EXIT_CODE_RE = /(?:Process exited with code|Exit code:)\s+(\d+)/;
133
+ function extractCodexEvents(raw) {
134
+ const out = [];
135
+ for (const lineRaw of raw.split("\n")) {
136
+ const line = lineRaw.trim();
137
+ if (!line)
138
+ continue;
139
+ let e;
140
+ try {
141
+ e = JSON.parse(line);
142
+ } catch {
143
+ continue;
144
+ }
145
+ if (e.type !== "response_item")
146
+ continue;
147
+ const p = e.payload ?? {};
148
+ const t = typeof e.timestamp === "string" ? Date.parse(e.timestamp) : NaN;
149
+ const ptype = p.type;
150
+ if (ptype === "message") {
151
+ const role = p.role;
152
+ const text = codexContentText(p.content).replace(SYSTEM_REMINDER_RE2, "").trim();
153
+ if (!text)
154
+ continue;
155
+ if (role === "user") {
156
+ if (isCodexInjectedUserText(text))
157
+ continue;
158
+ out.push({ kind: "user", t, text });
159
+ } else if (role === "assistant") {
160
+ out.push({ kind: "assistant", t, text });
161
+ }
162
+ continue;
163
+ }
164
+ if (ptype === "function_call" || ptype === "custom_tool_call" || ptype === "local_shell_call") {
165
+ const name = typeof p.name === "string" && p.name ? p.name : ptype === "local_shell_call" ? "shell" : String(ptype);
166
+ const input = typeof p.arguments === "string" ? p.arguments : typeof p.input === "string" ? p.input : "";
167
+ out.push({ kind: "tool", t, name, input });
168
+ continue;
169
+ }
170
+ if (ptype === "function_call_output" || ptype === "custom_tool_call_output") {
171
+ const output = typeof p.output === "string" ? p.output : "";
172
+ const m = EXIT_CODE_RE.exec(output.slice(0, 200));
173
+ out.push({ kind: "tool_output", t, output, isError: !!m && m[1] !== "0" });
174
+ }
175
+ }
176
+ return out;
177
+ }
178
+
179
+ // ../coding-core/dist/cursor.js
180
+ import { promises as fs } from "fs";
181
+ var NODE_SQLITE = "node:sqlite";
182
+ async function openCursorSqlite(dbPath) {
183
+ try {
184
+ await fs.access(dbPath);
185
+ } catch {
186
+ return null;
187
+ }
188
+ let mod;
189
+ try {
190
+ mod = await import(NODE_SQLITE);
191
+ } catch {
192
+ return null;
193
+ }
194
+ const DatabaseSync = mod?.DatabaseSync;
195
+ if (!DatabaseSync)
196
+ return null;
197
+ let db;
198
+ try {
199
+ db = new DatabaseSync(dbPath, { readOnly: true });
200
+ } catch {
201
+ try {
202
+ db = new DatabaseSync(dbPath);
203
+ } catch {
204
+ return null;
205
+ }
206
+ }
207
+ const asText = (v) => v == null ? null : typeof v === "string" ? v : Buffer.from(v).toString("utf-8");
208
+ return {
209
+ prefix(prefix) {
210
+ try {
211
+ const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE ? ESCAPE '\\'").all(likePrefix(prefix));
212
+ return rows.map((r) => ({ key: r.key, value: asText(r.value) ?? "" }));
213
+ } catch {
214
+ return [];
215
+ }
216
+ },
217
+ get(key) {
218
+ try {
219
+ const row = db.prepare("SELECT value FROM cursorDiskKV WHERE key = ?").get(key);
220
+ return row ? asText(row.value) : null;
221
+ } catch {
222
+ return null;
223
+ }
224
+ },
225
+ close() {
226
+ try {
227
+ db.close();
228
+ } catch {
229
+ }
230
+ }
231
+ };
232
+ }
233
+ function likePrefix(prefix) {
234
+ return prefix.replace(/[\\%_]/g, (c) => "\\" + c) + "%";
235
+ }
236
+ var CURSOR_USER_QUERY_RE = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/;
237
+ function cleanUserText(raw) {
238
+ let t = raw.replace(SYSTEM_REMINDER_RE, "");
239
+ const m = t.match(CURSOR_USER_QUERY_RE);
240
+ if (m)
241
+ t = m[1];
242
+ return t.trim();
243
+ }
244
+ var msFrom = (v) => {
245
+ if (typeof v === "number" && Number.isFinite(v))
246
+ return v;
247
+ if (typeof v === "string") {
248
+ const n = Date.parse(v);
249
+ return Number.isFinite(n) ? n : null;
250
+ }
251
+ return null;
252
+ };
253
+ function readCursorComposer(db, composerId) {
254
+ const cdRaw = db.get(`composerData:${composerId}`);
255
+ if (!cdRaw)
256
+ return null;
257
+ let cd;
258
+ try {
259
+ cd = JSON.parse(cdRaw);
260
+ } catch {
261
+ return null;
262
+ }
263
+ const headers = Array.isArray(cd.fullConversationHeadersOnly) ? cd.fullConversationHeadersOnly : [];
264
+ if (headers.length === 0)
265
+ return null;
266
+ const bubbles = /* @__PURE__ */ new Map();
267
+ for (const row of db.prefix(`bubbleId:${composerId}:`)) {
268
+ const bid = row.key.slice(`bubbleId:${composerId}:`.length);
269
+ try {
270
+ bubbles.set(bid, JSON.parse(row.value));
271
+ } catch {
272
+ }
273
+ }
274
+ const turns = [];
275
+ let clock = msFrom(cd.createdAt) ?? 0;
276
+ for (const h of headers) {
277
+ const b = bubbles.get(h.bubbleId);
278
+ if (!b)
279
+ continue;
280
+ const type = h.type === 1 ? 1 : 2;
281
+ const tool = type === 2 && b.toolFormerData && typeof b.toolFormerData.name === "string" ? b.toolFormerData.name : null;
282
+ const rawText = typeof b.text === "string" ? b.text : "";
283
+ const text = type === 1 ? cleanUserText(rawText) : rawText.replace(SYSTEM_REMINDER_RE, "").trim();
284
+ if (!text && !tool)
285
+ continue;
286
+ const t = msFrom(b.createdAt);
287
+ clock = Math.max(clock, t ?? clock + 1);
288
+ const model = b.modelInfo && typeof b.modelInfo.modelName === "string" ? b.modelInfo.modelName : null;
289
+ turns.push({ t: clock, type, text, tool, model });
290
+ }
291
+ if (turns.length === 0)
292
+ return null;
293
+ const gitRepo = Array.isArray(cd.trackedGitRepos) && cd.trackedGitRepos[0] && typeof cd.trackedGitRepos[0].repoPath === "string" ? cd.trackedGitRepos[0].repoPath : null;
294
+ return {
295
+ composerId,
296
+ createdAt: msFrom(cd.createdAt),
297
+ updatedAt: msFrom(cd.lastUpdatedAt),
298
+ name: typeof cd.name === "string" && cd.name.trim() ? cd.name.trim() : null,
299
+ gitRepo,
300
+ turns
301
+ };
302
+ }
303
+ function sniffCursorRaw(raw) {
304
+ for (const lineRaw of raw.split("\n")) {
305
+ const line = lineRaw.trim();
306
+ if (!line)
307
+ continue;
308
+ try {
309
+ const e = JSON.parse(line);
310
+ return (e.role === "user" || e.role === "assistant") && typeof e.message === "object" && e.message !== null && "content" in e.message && !("type" in e);
311
+ } catch {
312
+ return false;
313
+ }
314
+ }
315
+ return false;
316
+ }
317
+ function jsonlText(content) {
318
+ const parts = [];
319
+ const tools = [];
320
+ if (Array.isArray(content)) {
321
+ for (const item of content) {
322
+ if (!item || typeof item !== "object")
323
+ continue;
324
+ const it = item;
325
+ if (it.type === "text" && it.text)
326
+ parts.push(it.text);
327
+ else if (it.type === "tool_use" && it.name)
328
+ tools.push(it.name);
329
+ }
330
+ } else if (typeof content === "string")
331
+ parts.push(content);
332
+ return { text: parts.join("\n"), tools };
333
+ }
334
+ function parseCursorJsonl(raw, base) {
335
+ const turns = [];
336
+ let clock = base;
337
+ for (const lineRaw of raw.split("\n")) {
338
+ const line = lineRaw.trim();
339
+ if (!line)
340
+ continue;
341
+ let e;
342
+ try {
343
+ e = JSON.parse(line);
344
+ } catch {
345
+ continue;
346
+ }
347
+ if (e.role !== "user" && e.role !== "assistant")
348
+ continue;
349
+ const { text: rawText, tools } = jsonlText(e.message?.content);
350
+ const type = e.role === "user" ? 1 : 2;
351
+ const text = type === 1 ? cleanUserText(rawText) : rawText.replace(SYSTEM_REMINDER_RE, "").trim();
352
+ if (type === 2 && tools.length) {
353
+ for (const tool of tools) {
354
+ clock += 1;
355
+ turns.push({ t: clock, type: 2, text: "", tool, model: null });
356
+ }
357
+ }
358
+ if (!text)
359
+ continue;
360
+ clock += 1;
361
+ turns.push({ t: clock, type, text, tool: null, model: null });
362
+ }
363
+ if (turns.length === 0)
364
+ return null;
365
+ return { composerId: "", createdAt: base, updatedAt: clock, name: null, gitRepo: null, turns };
366
+ }
367
+ function cursorMessages(c) {
368
+ const out = [];
369
+ let aiStart = 0;
370
+ let aiParts = [];
371
+ const flushAI = () => {
372
+ if (!aiParts.length)
373
+ return;
374
+ out.push({ t: aiStart, role: "ai", text: aiParts.join("\n") });
375
+ aiParts = [];
376
+ };
377
+ for (const turn of c.turns) {
378
+ if (turn.type === 1) {
379
+ flushAI();
380
+ out.push({ t: turn.t, role: "user", text: turn.text });
381
+ } else {
382
+ if (aiParts.length === 0)
383
+ aiStart = turn.t;
384
+ if (turn.text)
385
+ aiParts.push(turn.text);
386
+ }
387
+ }
388
+ flushAI();
389
+ return out;
390
+ }
391
+ function composerIdFromFile(file) {
392
+ const m = file.match(/agent-transcripts\/([^/]+)\/[^/]+\.jsonl$/);
393
+ return m ? m[1] : null;
394
+ }
395
+ async function loadCursorComposer(file, raw, dbPath) {
396
+ const cid = composerIdFromFile(file);
397
+ if (cid && dbPath) {
398
+ const db = await openCursorSqlite(dbPath);
399
+ if (db) {
400
+ try {
401
+ const c = readCursorComposer(db, cid);
402
+ if (c)
403
+ return c;
404
+ } finally {
405
+ db.close();
406
+ }
407
+ }
408
+ }
409
+ return parseCursorJsonl(raw, 0);
410
+ }
411
+
412
+ // ../coding-core/dist/raw.js
413
+ import path from "path";
414
+ import os2 from "os";
415
+ var SOURCE_ROOT_ENV_VARS = [
416
+ "CLAUDE_PROJECTS_DIR",
417
+ "CODEX_SESSIONS_DIR",
418
+ "CURSOR_WORKSPACE_DIR",
419
+ "CURSOR_PROJECTS_DIR",
420
+ // Cursor's globalStorage dir (holds state.vscdb — the composer bubbles that
421
+ // carry a Cursor session's real per-turn timestamps + content). Same
422
+ // isolation contract: overriding any root disables every unset source.
423
+ "CURSOR_GLOBAL_DIR"
424
+ ];
425
+ var anyRootOverridden = () => SOURCE_ROOT_ENV_VARS.some((v) => !!process.env[v]);
426
+ function resolveSourceRoot(envVar, ...defaultSegments) {
427
+ const explicit = process.env[envVar];
428
+ if (explicit)
429
+ return explicit;
430
+ if (anyRootOverridden())
431
+ return null;
432
+ return path.join(os2.homedir(), ...defaultSegments);
433
+ }
434
+ var cursorGlobalRoot = () => resolveSourceRoot("CURSOR_GLOBAL_DIR", "Library", "Application Support", "Cursor", "User", "globalStorage");
435
+ function cursorGlobalDbPath() {
436
+ const root = cursorGlobalRoot();
437
+ return root === null ? null : path.join(root, "state.vscdb");
438
+ }
439
+ var MACHINE_CURSOR_FILE_RE = /^(.*[/\\]machines[/\\][^/\\]+[/\\]\.cursor)[/\\]projects[/\\]/;
440
+ function cursorDbPathForFile(file) {
441
+ const m = file.match(MACHINE_CURSOR_FILE_RE);
442
+ if (m)
443
+ return path.join(m[1], "globalStorage", "state.vscdb");
444
+ return cursorGlobalDbPath();
445
+ }
446
+
88
447
  // ../coding-core/dist/messages.js
89
448
  function extractText(content) {
90
449
  if (typeof content === "string")
@@ -106,13 +465,62 @@ function createMessageDeduper() {
106
465
  return true;
107
466
  };
108
467
  }
468
+ function extractMessagesCodex(raw) {
469
+ const out = [];
470
+ let aiProse = [];
471
+ let aiTools = {};
472
+ let aiStart = 0;
473
+ const flushAI = () => {
474
+ if (!aiProse.length && Object.keys(aiTools).length === 0)
475
+ return;
476
+ const toolStr = Object.entries(aiTools).map(([n, c]) => c > 1 ? `${n}\xD7${c}` : n).join(", ");
477
+ let prose = aiProse.join(" ").replace(/\s+/g, " ").trim();
478
+ if (prose.length > 2200)
479
+ prose = prose.slice(0, 2200) + "\u2026";
480
+ let text = prose;
481
+ if (toolStr)
482
+ text = (text ? text + " " : "") + `[ran ${toolStr}]`;
483
+ if (text)
484
+ out.push({ t: aiStart, role: "ai", text });
485
+ aiProse = [];
486
+ aiTools = {};
487
+ };
488
+ for (const ev of extractCodexEvents(raw)) {
489
+ if (!Number.isFinite(ev.t))
490
+ continue;
491
+ if (ev.kind === "user") {
492
+ const text = humanTypedText(ev.text);
493
+ if (text) {
494
+ flushAI();
495
+ out.push({ t: ev.t, role: "user", text });
496
+ }
497
+ } else if (ev.kind === "assistant") {
498
+ if (!aiProse.length && Object.keys(aiTools).length === 0)
499
+ aiStart = ev.t;
500
+ if (ev.text)
501
+ aiProse.push(ev.text);
502
+ } else if (ev.kind === "tool") {
503
+ if (!aiProse.length && Object.keys(aiTools).length === 0)
504
+ aiStart = ev.t;
505
+ aiTools[ev.name] = (aiTools[ev.name] ?? 0) + 1;
506
+ }
507
+ }
508
+ flushAI();
509
+ return out.sort((a, b) => a.t - b.t);
510
+ }
109
511
  async function extractMessages(file) {
110
512
  let raw;
111
513
  try {
112
- raw = await fs.readFile(file, "utf-8");
514
+ raw = await fs2.readFile(file, "utf-8");
113
515
  } catch {
114
516
  return [];
115
517
  }
518
+ if (sniffCodexRaw(raw))
519
+ return extractMessagesCodex(raw);
520
+ if (sniffCursorRaw(raw)) {
521
+ const c = await loadCursorComposer(file, raw, cursorDbPathForFile(file));
522
+ return c ? cursorMessages(c) : [];
523
+ }
116
524
  const out = [];
117
525
  const userTexts = /* @__PURE__ */ new Set();
118
526
  const seenUuids = /* @__PURE__ */ new Set();
@@ -279,9 +687,13 @@ function throughputCeiling(dayWords, proof = 3) {
279
687
  return 0;
280
688
  return scores[Math.min(proof, scores.length) - 1];
281
689
  }
690
+ function throughputMeanDays(activeDays, spanHByDate, spanHourMin) {
691
+ const substantial = activeDays.filter((d) => (spanHByDate[d] || 0) >= spanHourMin);
692
+ return substantial.length ? substantial : activeDays;
693
+ }
282
694
 
283
695
  // ../../lib/agents/coding/profile.ts
284
- import path from "path";
696
+ import path2 from "path";
285
697
 
286
698
  // ../../lib/calibration/fingerprint.ts
287
699
  import { createHash } from "node:crypto";
@@ -431,7 +843,7 @@ var IDLE_CAP_MS = 12 * 6e4;
431
843
 
432
844
  // ../../lib/calibration/index.ts
433
845
  var DEFAULT_CALIBRATION = {
434
- version: "2026-07-16.2",
846
+ version: "2026-07-21.1",
435
847
  // = RUBRIC_FINGERPRINT of the shipped npm rubric (packages/coding-analyzer/
436
848
  // src/grade/criteria.ts). A unit test fails loud when the rubric changes
437
849
  // without this being updated (and re-pushed to the server).
@@ -527,13 +939,14 @@ var DEFAULT_CALIBRATION = {
527
939
  },
528
940
  codingBenchmarks: {
529
941
  flow: {
530
- // typical knowledge worker ≈ 35% of an 8h day focused; ~4h/day is the
531
- // recognized deep-work ceiling (Newport) 50% most sit far below it.
942
+ // typical knowledge worker ≈ 35% of an 8h day focused; the ~4h/day
943
+ // deep-work ceiling (Newport) sits nearer ~40% of a real (9-10h) top
944
+ // performer's day — 50% overstated it (owner call 2026-07-21).
532
945
  typicalPctOfDay: 0.35,
533
- topPctOfDay: 0.5,
946
+ topPctOfDay: 0.4,
534
947
  tag: "measured",
535
- source: "RescueTime 2019 (~2h48m focused/day) \xB7 Cal Newport, Deep Work (~4h/day deep-work ceiling)",
536
- line: "the best spend ~half the workday in true deep flow; ~4h/day is the human ceiling"
948
+ source: "RescueTime 2019 (~2h48m focused/day) \xB7 Cal Newport, Deep Work (~4h/day deep-work ceiling against a 9-10h day)",
949
+ line: "the best spend ~40% of the workday in true deep flow; ~4h/day is the human ceiling"
537
950
  },
538
951
  longestRun: {
539
952
  typicalHours: 0.67,
@@ -582,15 +995,15 @@ var BEST = DEFAULT_CALIBRATION.codingBenchmarks;
582
995
 
583
996
  // ../../lib/agents/coding/profile.ts
584
997
  var ROOT = process.cwd();
585
- var CODING_DIR = process.env.POLYMATH_DATA_DIR ? path.join(process.env.POLYMATH_DATA_DIR, "coding") : path.join(ROOT, ".data", "coding");
586
- var GRADES = path.join(CODING_DIR, "grades");
998
+ var CODING_DIR = process.env.POLYMATH_DATA_DIR ? path2.join(process.env.POLYMATH_DATA_DIR, "coding") : path2.join(ROOT, ".data", "coding");
999
+ var GRADES = path2.join(CODING_DIR, "grades");
587
1000
 
588
1001
  // ../../scripts/coding-aggregate.mts
589
1002
  var CODING = CODING_DIR;
590
1003
  var TZ = "America/Los_Angeles";
591
1004
  var readJson = async (f, d) => {
592
1005
  try {
593
- return JSON.parse(await fs2.readFile(path2.join(CODING, f), "utf8"));
1006
+ return JSON.parse(await fs3.readFile(path3.join(CODING, f), "utf8"));
594
1007
  } catch {
595
1008
  return d;
596
1009
  }
@@ -602,7 +1015,7 @@ async function throughputAvg(spanHourMin) {
602
1015
  const slots = Object.keys(d.slots || {}).map(Number);
603
1016
  spanH[d.date] = slots.length ? (Math.max(...slots) - Math.min(...slots) + 1) / 6 : 0;
604
1017
  }
605
- const sess = JSON.parse(await fs2.readFile(path2.join(CODING, "sessions.json"), "utf8")).filter((s) => !s.duplicateOf && s.klass === "interactive");
1018
+ const sess = JSON.parse(await fs3.readFile(path3.join(CODING, "sessions.json"), "utf8")).filter((s) => !s.duplicateOf && s.klass === "interactive");
606
1019
  const wordsByDay = {};
607
1020
  const firstSeen = createMessageDeduper();
608
1021
  for (const s of sess) {
@@ -620,7 +1033,7 @@ async function throughputAvg(spanHourMin) {
620
1033
  const dates = Object.keys(wordsByDay).filter((d) => wordsByDay[d] > 0);
621
1034
  const mean = (a) => a.length ? Math.round(a.reduce((x, y) => x + y, 0) / a.length) : 0;
622
1035
  const substantial = dates.filter((d) => (spanH[d] || 0) >= spanHourMin);
623
- const avgWords = mean(substantial.map((d) => wordsByDay[d]));
1036
+ const avgWords = mean(throughputMeanDays(dates, spanH, spanHourMin).map((d) => wordsByDay[d]));
624
1037
  const sens = {};
625
1038
  for (const h of [2, 3, 4, 5, 6]) {
626
1039
  const ds = dates.filter((d) => (spanH[d] || 0) >= h);
@@ -632,14 +1045,14 @@ async function throughputAvg(spanHourMin) {
632
1045
  return { spanHourMin, substantialDays: substantial.length, totalCodingDays: dates.length, avgWordsPerDay: avgWords, score, label, ceiling: throughputCeiling(dates.map((d) => wordsByDay[d])), daySensitivity: sens };
633
1046
  }
634
1047
  async function abilityCeiling() {
635
- const dir = path2.join(CODING, "grades");
636
- const files = (await fs2.readdir(dir).catch(() => [])).filter((f) => f.endsWith(".json"));
1048
+ const dir = path3.join(CODING, "grades");
1049
+ const files = (await fs3.readdir(dir).catch(() => [])).filter((f) => f.endsWith(".json"));
637
1050
  const collect = (key) => [];
638
1051
  const byKey = { generative: [], taste: [] };
639
1052
  for (const f of files) {
640
1053
  let j;
641
1054
  try {
642
- j = JSON.parse(await fs2.readFile(path2.join(dir, f), "utf8"));
1055
+ j = JSON.parse(await fs3.readFile(path3.join(dir, f), "utf8"));
643
1056
  } catch {
644
1057
  continue;
645
1058
  }
@@ -695,10 +1108,11 @@ async function flowStats() {
695
1108
  }
696
1109
  }
697
1110
  const peakWindow = bestSum > 0 ? `${hm(bestStart * 6)}\u2013${hm((bestStart + 3) * 6)}` : null;
698
- const perDay = conc.perDay || {};
1111
+ const perDayRaw = conc.perDay || {};
1112
+ const perDay = Array.isArray(perDayRaw) ? Object.fromEntries(perDayRaw.filter((r) => r?.date).map((r) => [r.date, r])) : perDayRaw;
699
1113
  const concOf = (date) => {
700
1114
  const v = perDay[date];
701
- return typeof v === "number" ? v : v?.peak ?? v?.max ?? v?.maxConcurrency ?? 1;
1115
+ return typeof v === "number" ? v : v?.peakConcurrency ?? v?.peak ?? v?.max ?? v?.maxConcurrency ?? 1;
702
1116
  };
703
1117
  const tiers = { "solo (\xD71)": { fracs: [] }, "light (\xD72\u20133)": { fracs: [] }, "heavy (\xD74+)": { fracs: [] } };
704
1118
  for (const d of days) {
@@ -714,9 +1128,15 @@ async function flowStats() {
714
1128
  const pctOfWorkDay = workDayHours > 0 ? Math.round(flowHoursPerDay / workDayHours * 100) / 100 : null;
715
1129
  const bestTier = [...byParallelism].sort((a, b) => b.avgFlowFraction - a.avgFlowFraction)[0];
716
1130
  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.";
1131
+ const avgConcurrent = (hist) => {
1132
+ const total = Object.values(hist || {}).reduce((a, v) => a + Number(v || 0), 0);
1133
+ if (total <= 0) return 0;
1134
+ const weighted = Object.entries(hist || {}).reduce((a, [k, v]) => a + Number(k) * Number(v || 0), 0);
1135
+ return Math.round(weighted / total * 10) / 10;
1136
+ };
717
1137
  return {
718
1138
  workWindow: { typicalStart: starts.length ? hm(med(starts)) : null, typicalEnd: ends.length ? hm(med(ends)) : null, activeDays: days.length },
719
- parallelism: { maxConcurrent: conc.maxConcurrency || 0 },
1139
+ parallelism: { maxConcurrent: conc.maxConcurrency || 0, avgConcurrent: avgConcurrent(conc.levelHistogramMin || {}) },
720
1140
  flow: { fractionOfActiveTime: flow.totals?.flowFraction ?? null, pctOfWorkDay, workDayHours, flowHoursPerDay, totalFlowHours },
721
1141
  peakWindow,
722
1142
  flowByHour,
@@ -730,7 +1150,7 @@ async function main() {
730
1150
  const throughput = await throughputAvg(4);
731
1151
  const prev = await readJson("aggregate.json", {});
732
1152
  const out = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ability, flow, throughput, ...prev.bigPicture ? { bigPicture: prev.bigPicture, ...prev.bigPictureSha ? { bigPictureSha: prev.bigPictureSha } : {} } : {} };
733
- await fs2.writeFile(path2.join(CODING, "aggregate.json"), JSON.stringify(out, null, 2));
1153
+ await fs3.writeFile(path3.join(CODING, "aggregate.json"), JSON.stringify(out, null, 2));
734
1154
  console.log("aggregate.json written" + (prev.bigPicture ? " (bigPicture preserved)" : " (no bigPicture present \u2014 run coding-nutshell.mts)"));
735
1155
  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}]`);
736
1156
  console.log(` day sensitivity:`, throughput.daySensitivity);