polymath-society 0.2.20 → 0.2.22
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/dist/cli.js +23943 -21630
- package/dist/engine/chat-loops.js +8 -1
- package/dist/engine/exp-allfacets.js +8 -1
- package/dist/engine/exp-person.js +8 -1
- package/dist/engine/exp-pipeline.js +8 -1
- package/dist/engine/ingest-export.js +7 -6
- package/dist/engine/peak-demos.js +8 -1
- package/dist/engine/person-dimension-summary.js +8 -1
- package/dist/engine/person-facet-lines.js +8 -1
- package/dist/engine/person-headline.js +8 -1
- package/dist/engine/person-report.js +8 -1
- package/dist/engine/person-self-image.js +8 -1
- package/dist/engine/public-report.js +15 -7
- package/dist/engine/run-analysis.js +8 -1
- package/dist/engine/run-tagger.js +8 -1
- package/dist/index.js +19647 -17969
- package/dist/pipeline/coding-agglomerate.js +826 -82
- package/dist/pipeline/coding-aggregate.js +307 -23
- package/dist/pipeline/coding-build.js +453 -95
- package/dist/pipeline/coding-coaching.js +479 -88
- package/dist/pipeline/coding-day-digest.js +308 -36
- package/dist/pipeline/coding-delegation.js +376 -52
- package/dist/pipeline/coding-expertise.js +356 -62
- package/dist/pipeline/coding-flow.js +288 -15
- package/dist/pipeline/coding-focus.js +371 -51
- package/dist/pipeline/coding-frontier-detail.js +344 -50
- package/dist/pipeline/coding-frontier.js +7 -6
- package/dist/pipeline/coding-gap-dist.js +288 -15
- package/dist/pipeline/coding-gap.js +507 -115
- package/dist/pipeline/coding-grade.js +342 -45
- package/dist/pipeline/coding-growth.js +17041 -0
- package/dist/pipeline/coding-nutshell.js +15 -7
- package/dist/pipeline/coding-projects.js +7 -6
- package/dist/pipeline/coding-walkthrough.js +317 -37
- package/dist/pipeline/coding-workbrief.js +16387 -0
- package/dist/web/app.js +2095 -1395
- package/dist/web/styles.css +1 -1
- 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
|
|
5
|
-
import
|
|
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
|
|
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;
|
|
@@ -176,6 +176,274 @@ function extractCodexEvents(raw) {
|
|
|
176
176
|
return out;
|
|
177
177
|
}
|
|
178
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
|
+
|
|
179
447
|
// ../coding-core/dist/messages.js
|
|
180
448
|
function extractText(content) {
|
|
181
449
|
if (typeof content === "string")
|
|
@@ -243,12 +511,16 @@ function extractMessagesCodex(raw) {
|
|
|
243
511
|
async function extractMessages(file) {
|
|
244
512
|
let raw;
|
|
245
513
|
try {
|
|
246
|
-
raw = await
|
|
514
|
+
raw = await fs2.readFile(file, "utf-8");
|
|
247
515
|
} catch {
|
|
248
516
|
return [];
|
|
249
517
|
}
|
|
250
518
|
if (sniffCodexRaw(raw))
|
|
251
519
|
return extractMessagesCodex(raw);
|
|
520
|
+
if (sniffCursorRaw(raw)) {
|
|
521
|
+
const c = await loadCursorComposer(file, raw, cursorDbPathForFile(file));
|
|
522
|
+
return c ? cursorMessages(c) : [];
|
|
523
|
+
}
|
|
252
524
|
const out = [];
|
|
253
525
|
const userTexts = /* @__PURE__ */ new Set();
|
|
254
526
|
const seenUuids = /* @__PURE__ */ new Set();
|
|
@@ -415,9 +687,13 @@ function throughputCeiling(dayWords, proof = 3) {
|
|
|
415
687
|
return 0;
|
|
416
688
|
return scores[Math.min(proof, scores.length) - 1];
|
|
417
689
|
}
|
|
690
|
+
function throughputMeanDays(activeDays, spanHByDate, spanHourMin) {
|
|
691
|
+
const substantial = activeDays.filter((d) => (spanHByDate[d] || 0) >= spanHourMin);
|
|
692
|
+
return substantial.length ? substantial : activeDays;
|
|
693
|
+
}
|
|
418
694
|
|
|
419
695
|
// ../../lib/agents/coding/profile.ts
|
|
420
|
-
import
|
|
696
|
+
import path2 from "path";
|
|
421
697
|
|
|
422
698
|
// ../../lib/calibration/fingerprint.ts
|
|
423
699
|
import { createHash } from "node:crypto";
|
|
@@ -567,7 +843,7 @@ var IDLE_CAP_MS = 12 * 6e4;
|
|
|
567
843
|
|
|
568
844
|
// ../../lib/calibration/index.ts
|
|
569
845
|
var DEFAULT_CALIBRATION = {
|
|
570
|
-
version: "2026-07-
|
|
846
|
+
version: "2026-07-21.1",
|
|
571
847
|
// = RUBRIC_FINGERPRINT of the shipped npm rubric (packages/coding-analyzer/
|
|
572
848
|
// src/grade/criteria.ts). A unit test fails loud when the rubric changes
|
|
573
849
|
// without this being updated (and re-pushed to the server).
|
|
@@ -663,13 +939,14 @@ var DEFAULT_CALIBRATION = {
|
|
|
663
939
|
},
|
|
664
940
|
codingBenchmarks: {
|
|
665
941
|
flow: {
|
|
666
|
-
// typical knowledge worker ≈ 35% of an 8h day focused; ~4h/day
|
|
667
|
-
//
|
|
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).
|
|
668
945
|
typicalPctOfDay: 0.35,
|
|
669
|
-
topPctOfDay: 0.
|
|
946
|
+
topPctOfDay: 0.4,
|
|
670
947
|
tag: "measured",
|
|
671
|
-
source: "RescueTime 2019 (~2h48m focused/day) \xB7 Cal Newport, Deep Work (~4h/day deep-work ceiling)",
|
|
672
|
-
line: "the best spend ~
|
|
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"
|
|
673
950
|
},
|
|
674
951
|
longestRun: {
|
|
675
952
|
typicalHours: 0.67,
|
|
@@ -718,15 +995,15 @@ var BEST = DEFAULT_CALIBRATION.codingBenchmarks;
|
|
|
718
995
|
|
|
719
996
|
// ../../lib/agents/coding/profile.ts
|
|
720
997
|
var ROOT = process.cwd();
|
|
721
|
-
var CODING_DIR = process.env.POLYMATH_DATA_DIR ?
|
|
722
|
-
var 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");
|
|
723
1000
|
|
|
724
1001
|
// ../../scripts/coding-aggregate.mts
|
|
725
1002
|
var CODING = CODING_DIR;
|
|
726
1003
|
var TZ = "America/Los_Angeles";
|
|
727
1004
|
var readJson = async (f, d) => {
|
|
728
1005
|
try {
|
|
729
|
-
return JSON.parse(await
|
|
1006
|
+
return JSON.parse(await fs3.readFile(path3.join(CODING, f), "utf8"));
|
|
730
1007
|
} catch {
|
|
731
1008
|
return d;
|
|
732
1009
|
}
|
|
@@ -738,7 +1015,7 @@ async function throughputAvg(spanHourMin) {
|
|
|
738
1015
|
const slots = Object.keys(d.slots || {}).map(Number);
|
|
739
1016
|
spanH[d.date] = slots.length ? (Math.max(...slots) - Math.min(...slots) + 1) / 6 : 0;
|
|
740
1017
|
}
|
|
741
|
-
const sess = JSON.parse(await
|
|
1018
|
+
const sess = JSON.parse(await fs3.readFile(path3.join(CODING, "sessions.json"), "utf8")).filter((s) => !s.duplicateOf && s.klass === "interactive");
|
|
742
1019
|
const wordsByDay = {};
|
|
743
1020
|
const firstSeen = createMessageDeduper();
|
|
744
1021
|
for (const s of sess) {
|
|
@@ -756,7 +1033,7 @@ async function throughputAvg(spanHourMin) {
|
|
|
756
1033
|
const dates = Object.keys(wordsByDay).filter((d) => wordsByDay[d] > 0);
|
|
757
1034
|
const mean = (a) => a.length ? Math.round(a.reduce((x, y) => x + y, 0) / a.length) : 0;
|
|
758
1035
|
const substantial = dates.filter((d) => (spanH[d] || 0) >= spanHourMin);
|
|
759
|
-
const avgWords = mean(
|
|
1036
|
+
const avgWords = mean(throughputMeanDays(dates, spanH, spanHourMin).map((d) => wordsByDay[d]));
|
|
760
1037
|
const sens = {};
|
|
761
1038
|
for (const h of [2, 3, 4, 5, 6]) {
|
|
762
1039
|
const ds = dates.filter((d) => (spanH[d] || 0) >= h);
|
|
@@ -768,14 +1045,14 @@ async function throughputAvg(spanHourMin) {
|
|
|
768
1045
|
return { spanHourMin, substantialDays: substantial.length, totalCodingDays: dates.length, avgWordsPerDay: avgWords, score, label, ceiling: throughputCeiling(dates.map((d) => wordsByDay[d])), daySensitivity: sens };
|
|
769
1046
|
}
|
|
770
1047
|
async function abilityCeiling() {
|
|
771
|
-
const dir =
|
|
772
|
-
const files = (await
|
|
1048
|
+
const dir = path3.join(CODING, "grades");
|
|
1049
|
+
const files = (await fs3.readdir(dir).catch(() => [])).filter((f) => f.endsWith(".json"));
|
|
773
1050
|
const collect = (key) => [];
|
|
774
1051
|
const byKey = { generative: [], taste: [] };
|
|
775
1052
|
for (const f of files) {
|
|
776
1053
|
let j;
|
|
777
1054
|
try {
|
|
778
|
-
j = JSON.parse(await
|
|
1055
|
+
j = JSON.parse(await fs3.readFile(path3.join(dir, f), "utf8"));
|
|
779
1056
|
} catch {
|
|
780
1057
|
continue;
|
|
781
1058
|
}
|
|
@@ -831,10 +1108,11 @@ async function flowStats() {
|
|
|
831
1108
|
}
|
|
832
1109
|
}
|
|
833
1110
|
const peakWindow = bestSum > 0 ? `${hm(bestStart * 6)}\u2013${hm((bestStart + 3) * 6)}` : null;
|
|
834
|
-
const
|
|
1111
|
+
const perDayRaw = conc.perDay || {};
|
|
1112
|
+
const perDay = Array.isArray(perDayRaw) ? Object.fromEntries(perDayRaw.filter((r) => r?.date).map((r) => [r.date, r])) : perDayRaw;
|
|
835
1113
|
const concOf = (date) => {
|
|
836
1114
|
const v = perDay[date];
|
|
837
|
-
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;
|
|
838
1116
|
};
|
|
839
1117
|
const tiers = { "solo (\xD71)": { fracs: [] }, "light (\xD72\u20133)": { fracs: [] }, "heavy (\xD74+)": { fracs: [] } };
|
|
840
1118
|
for (const d of days) {
|
|
@@ -850,9 +1128,15 @@ async function flowStats() {
|
|
|
850
1128
|
const pctOfWorkDay = workDayHours > 0 ? Math.round(flowHoursPerDay / workDayHours * 100) / 100 : null;
|
|
851
1129
|
const bestTier = [...byParallelism].sort((a, b) => b.avgFlowFraction - a.avgFlowFraction)[0];
|
|
852
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
|
+
};
|
|
853
1137
|
return {
|
|
854
1138
|
workWindow: { typicalStart: starts.length ? hm(med(starts)) : null, typicalEnd: ends.length ? hm(med(ends)) : null, activeDays: days.length },
|
|
855
|
-
parallelism: { maxConcurrent: conc.maxConcurrency || 0 },
|
|
1139
|
+
parallelism: { maxConcurrent: conc.maxConcurrency || 0, avgConcurrent: avgConcurrent(conc.levelHistogramMin || {}) },
|
|
856
1140
|
flow: { fractionOfActiveTime: flow.totals?.flowFraction ?? null, pctOfWorkDay, workDayHours, flowHoursPerDay, totalFlowHours },
|
|
857
1141
|
peakWindow,
|
|
858
1142
|
flowByHour,
|
|
@@ -866,7 +1150,7 @@ async function main() {
|
|
|
866
1150
|
const throughput = await throughputAvg(4);
|
|
867
1151
|
const prev = await readJson("aggregate.json", {});
|
|
868
1152
|
const out = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ability, flow, throughput, ...prev.bigPicture ? { bigPicture: prev.bigPicture, ...prev.bigPictureSha ? { bigPictureSha: prev.bigPictureSha } : {} } : {} };
|
|
869
|
-
await
|
|
1153
|
+
await fs3.writeFile(path3.join(CODING, "aggregate.json"), JSON.stringify(out, null, 2));
|
|
870
1154
|
console.log("aggregate.json written" + (prev.bigPicture ? " (bigPicture preserved)" : " (no bigPicture present \u2014 run coding-nutshell.mts)"));
|
|
871
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}]`);
|
|
872
1156
|
console.log(` day sensitivity:`, throughput.daySensitivity);
|