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.
Files changed (38) hide show
  1. package/dist/cli.js +23943 -21630
  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 +19647 -17969
  17. package/dist/pipeline/coding-agglomerate.js +826 -82
  18. package/dist/pipeline/coding-aggregate.js +307 -23
  19. package/dist/pipeline/coding-build.js +453 -95
  20. package/dist/pipeline/coding-coaching.js +479 -88
  21. package/dist/pipeline/coding-day-digest.js +308 -36
  22. package/dist/pipeline/coding-delegation.js +376 -52
  23. package/dist/pipeline/coding-expertise.js +356 -62
  24. package/dist/pipeline/coding-flow.js +288 -15
  25. package/dist/pipeline/coding-focus.js +371 -51
  26. package/dist/pipeline/coding-frontier-detail.js +344 -50
  27. package/dist/pipeline/coding-frontier.js +7 -6
  28. package/dist/pipeline/coding-gap-dist.js +288 -15
  29. package/dist/pipeline/coding-gap.js +507 -115
  30. package/dist/pipeline/coding-grade.js +342 -45
  31. package/dist/pipeline/coding-growth.js +17041 -0
  32. package/dist/pipeline/coding-nutshell.js +15 -7
  33. package/dist/pipeline/coding-projects.js +7 -6
  34. package/dist/pipeline/coding-walkthrough.js +317 -37
  35. package/dist/pipeline/coding-workbrief.js +16387 -0
  36. package/dist/web/app.js +2095 -1395
  37. package/dist/web/styles.css +1 -1
  38. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
1
  import{createRequire as __cr}from'module';const require=__cr(import.meta.url);
2
2
 
3
3
  // ../../scripts/coding-build.mts
4
- import { promises as fs3 } from "fs";
4
+ import { promises as fs4 } from "fs";
5
5
  import path3 from "path";
6
6
 
7
7
  // ../coding-core/dist/raw.js
8
- import { promises as fs } from "fs";
8
+ import { promises as fs2 } from "fs";
9
9
  import path from "path";
10
10
  import os from "os";
11
11
 
@@ -133,12 +133,225 @@ function extractCodexEvents(raw) {
133
133
  return out;
134
134
  }
135
135
 
136
+ // ../coding-core/dist/cursor.js
137
+ import { promises as fs } from "fs";
138
+ var NODE_SQLITE = "node:sqlite";
139
+ async function openCursorSqlite(dbPath) {
140
+ try {
141
+ await fs.access(dbPath);
142
+ } catch {
143
+ return null;
144
+ }
145
+ let mod;
146
+ try {
147
+ mod = await import(NODE_SQLITE);
148
+ } catch {
149
+ return null;
150
+ }
151
+ const DatabaseSync = mod?.DatabaseSync;
152
+ if (!DatabaseSync)
153
+ return null;
154
+ let db;
155
+ try {
156
+ db = new DatabaseSync(dbPath, { readOnly: true });
157
+ } catch {
158
+ try {
159
+ db = new DatabaseSync(dbPath);
160
+ } catch {
161
+ return null;
162
+ }
163
+ }
164
+ const asText = (v) => v == null ? null : typeof v === "string" ? v : Buffer.from(v).toString("utf-8");
165
+ return {
166
+ prefix(prefix) {
167
+ try {
168
+ const rows = db.prepare("SELECT key, value FROM cursorDiskKV WHERE key LIKE ? ESCAPE '\\'").all(likePrefix(prefix));
169
+ return rows.map((r) => ({ key: r.key, value: asText(r.value) ?? "" }));
170
+ } catch {
171
+ return [];
172
+ }
173
+ },
174
+ get(key) {
175
+ try {
176
+ const row = db.prepare("SELECT value FROM cursorDiskKV WHERE key = ?").get(key);
177
+ return row ? asText(row.value) : null;
178
+ } catch {
179
+ return null;
180
+ }
181
+ },
182
+ close() {
183
+ try {
184
+ db.close();
185
+ } catch {
186
+ }
187
+ }
188
+ };
189
+ }
190
+ function likePrefix(prefix) {
191
+ return prefix.replace(/[\\%_]/g, (c) => "\\" + c) + "%";
192
+ }
193
+ var CURSOR_USER_QUERY_RE = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/;
194
+ function cleanUserText(raw) {
195
+ let t = raw.replace(SYSTEM_REMINDER_RE, "");
196
+ const m = t.match(CURSOR_USER_QUERY_RE);
197
+ if (m)
198
+ t = m[1];
199
+ return t.trim();
200
+ }
201
+ var msFrom = (v) => {
202
+ if (typeof v === "number" && Number.isFinite(v))
203
+ return v;
204
+ if (typeof v === "string") {
205
+ const n = Date.parse(v);
206
+ return Number.isFinite(n) ? n : null;
207
+ }
208
+ return null;
209
+ };
210
+ function readCursorComposer(db, composerId) {
211
+ const cdRaw = db.get(`composerData:${composerId}`);
212
+ if (!cdRaw)
213
+ return null;
214
+ let cd;
215
+ try {
216
+ cd = JSON.parse(cdRaw);
217
+ } catch {
218
+ return null;
219
+ }
220
+ const headers = Array.isArray(cd.fullConversationHeadersOnly) ? cd.fullConversationHeadersOnly : [];
221
+ if (headers.length === 0)
222
+ return null;
223
+ const bubbles = /* @__PURE__ */ new Map();
224
+ for (const row of db.prefix(`bubbleId:${composerId}:`)) {
225
+ const bid = row.key.slice(`bubbleId:${composerId}:`.length);
226
+ try {
227
+ bubbles.set(bid, JSON.parse(row.value));
228
+ } catch {
229
+ }
230
+ }
231
+ const turns = [];
232
+ let clock = msFrom(cd.createdAt) ?? 0;
233
+ for (const h of headers) {
234
+ const b = bubbles.get(h.bubbleId);
235
+ if (!b)
236
+ continue;
237
+ const type = h.type === 1 ? 1 : 2;
238
+ const tool = type === 2 && b.toolFormerData && typeof b.toolFormerData.name === "string" ? b.toolFormerData.name : null;
239
+ const rawText = typeof b.text === "string" ? b.text : "";
240
+ const text = type === 1 ? cleanUserText(rawText) : rawText.replace(SYSTEM_REMINDER_RE, "").trim();
241
+ if (!text && !tool)
242
+ continue;
243
+ const t = msFrom(b.createdAt);
244
+ clock = Math.max(clock, t ?? clock + 1);
245
+ const model = b.modelInfo && typeof b.modelInfo.modelName === "string" ? b.modelInfo.modelName : null;
246
+ turns.push({ t: clock, type, text, tool, model });
247
+ }
248
+ if (turns.length === 0)
249
+ return null;
250
+ const gitRepo = Array.isArray(cd.trackedGitRepos) && cd.trackedGitRepos[0] && typeof cd.trackedGitRepos[0].repoPath === "string" ? cd.trackedGitRepos[0].repoPath : null;
251
+ return {
252
+ composerId,
253
+ createdAt: msFrom(cd.createdAt),
254
+ updatedAt: msFrom(cd.lastUpdatedAt),
255
+ name: typeof cd.name === "string" && cd.name.trim() ? cd.name.trim() : null,
256
+ gitRepo,
257
+ turns
258
+ };
259
+ }
260
+ function sniffCursorRaw(raw) {
261
+ for (const lineRaw of raw.split("\n")) {
262
+ const line = lineRaw.trim();
263
+ if (!line)
264
+ continue;
265
+ try {
266
+ const e = JSON.parse(line);
267
+ return (e.role === "user" || e.role === "assistant") && typeof e.message === "object" && e.message !== null && "content" in e.message && !("type" in e);
268
+ } catch {
269
+ return false;
270
+ }
271
+ }
272
+ return false;
273
+ }
274
+ function jsonlText(content) {
275
+ const parts = [];
276
+ const tools = [];
277
+ if (Array.isArray(content)) {
278
+ for (const item of content) {
279
+ if (!item || typeof item !== "object")
280
+ continue;
281
+ const it = item;
282
+ if (it.type === "text" && it.text)
283
+ parts.push(it.text);
284
+ else if (it.type === "tool_use" && it.name)
285
+ tools.push(it.name);
286
+ }
287
+ } else if (typeof content === "string")
288
+ parts.push(content);
289
+ return { text: parts.join("\n"), tools };
290
+ }
291
+ function parseCursorJsonl(raw, base) {
292
+ const turns = [];
293
+ let clock = base;
294
+ for (const lineRaw of raw.split("\n")) {
295
+ const line = lineRaw.trim();
296
+ if (!line)
297
+ continue;
298
+ let e;
299
+ try {
300
+ e = JSON.parse(line);
301
+ } catch {
302
+ continue;
303
+ }
304
+ if (e.role !== "user" && e.role !== "assistant")
305
+ continue;
306
+ const { text: rawText, tools } = jsonlText(e.message?.content);
307
+ const type = e.role === "user" ? 1 : 2;
308
+ const text = type === 1 ? cleanUserText(rawText) : rawText.replace(SYSTEM_REMINDER_RE, "").trim();
309
+ if (type === 2 && tools.length) {
310
+ for (const tool of tools) {
311
+ clock += 1;
312
+ turns.push({ t: clock, type: 2, text: "", tool, model: null });
313
+ }
314
+ }
315
+ if (!text)
316
+ continue;
317
+ clock += 1;
318
+ turns.push({ t: clock, type, text, tool: null, model: null });
319
+ }
320
+ if (turns.length === 0)
321
+ return null;
322
+ return { composerId: "", createdAt: base, updatedAt: clock, name: null, gitRepo: null, turns };
323
+ }
324
+ function composerIdFromFile(file) {
325
+ const m = file.match(/agent-transcripts\/([^/]+)\/[^/]+\.jsonl$/);
326
+ return m ? m[1] : null;
327
+ }
328
+ async function loadCursorComposer(file, raw, dbPath) {
329
+ const cid = composerIdFromFile(file);
330
+ if (cid && dbPath) {
331
+ const db = await openCursorSqlite(dbPath);
332
+ if (db) {
333
+ try {
334
+ const c = readCursorComposer(db, cid);
335
+ if (c)
336
+ return c;
337
+ } finally {
338
+ db.close();
339
+ }
340
+ }
341
+ }
342
+ return parseCursorJsonl(raw, 0);
343
+ }
344
+
136
345
  // ../coding-core/dist/raw.js
137
346
  var SOURCE_ROOT_ENV_VARS = [
138
347
  "CLAUDE_PROJECTS_DIR",
139
348
  "CODEX_SESSIONS_DIR",
140
349
  "CURSOR_WORKSPACE_DIR",
141
- "CURSOR_PROJECTS_DIR"
350
+ "CURSOR_PROJECTS_DIR",
351
+ // Cursor's globalStorage dir (holds state.vscdb — the composer bubbles that
352
+ // carry a Cursor session's real per-turn timestamps + content). Same
353
+ // isolation contract: overriding any root disables every unset source.
354
+ "CURSOR_GLOBAL_DIR"
142
355
  ];
143
356
  var anyRootOverridden = () => SOURCE_ROOT_ENV_VARS.some((v) => !!process.env[v]);
144
357
  function resolveSourceRoot(envVar, ...defaultSegments) {
@@ -153,6 +366,18 @@ var claudeProjectsRoot = () => resolveSourceRoot("CLAUDE_PROJECTS_DIR", ".claude
153
366
  var codexSessionsRoot = () => resolveSourceRoot("CODEX_SESSIONS_DIR", ".codex", "sessions");
154
367
  var cursorWorkspaceRoot = () => resolveSourceRoot("CURSOR_WORKSPACE_DIR", "Library", "Application Support", "Cursor", "User", "workspaceStorage");
155
368
  var cursorProjectsRoot = () => resolveSourceRoot("CURSOR_PROJECTS_DIR", ".cursor", "projects");
369
+ var cursorGlobalRoot = () => resolveSourceRoot("CURSOR_GLOBAL_DIR", "Library", "Application Support", "Cursor", "User", "globalStorage");
370
+ function cursorGlobalDbPath() {
371
+ const root = cursorGlobalRoot();
372
+ return root === null ? null : path.join(root, "state.vscdb");
373
+ }
374
+ var MACHINE_CURSOR_FILE_RE = /^(.*[/\\]machines[/\\][^/\\]+[/\\]\.cursor)[/\\]projects[/\\]/;
375
+ function cursorDbPathForFile(file) {
376
+ const m = file.match(MACHINE_CURSOR_FILE_RE);
377
+ if (m)
378
+ return path.join(m[1], "globalStorage", "state.vscdb");
379
+ return cursorGlobalDbPath();
380
+ }
156
381
  var SKIP_DIR_RE = /(private-var-folders|-T-ps-|--data(-|$)|-sources(-|$)|--conversations|-node-modules-|-tmp(-|$)|-polymath-rehearsal-|-polymath-overnight-)/;
157
382
  var AGENT_CWD_RE = /(\/\.data(\/|$)|\.polymath-society|\/sources(\/|$)|\/var\/folders\/|\/T\/ps-|\/timeline(\/|$)|onboarding-digest|engine-pilot|polymath-rehearsal|polymath-overnight)/;
158
383
  var PERSONA_RE = /^You are (a|an|the|\w+ing)\b/;
@@ -454,14 +679,13 @@ function parseCodex(raw, file) {
454
679
  s.klassReason = c.reason;
455
680
  return s;
456
681
  }
457
- async function readClaudeCode() {
458
- const base = claudeProjectsRoot();
682
+ async function readClaudeCodeAt(base, machine) {
459
683
  const out = [];
460
684
  if (!base)
461
685
  return out;
462
686
  let dirs = [];
463
687
  try {
464
- dirs = await fs.readdir(base);
688
+ dirs = await fs2.readdir(base);
465
689
  } catch {
466
690
  return out;
467
691
  }
@@ -471,7 +695,7 @@ async function readClaudeCode() {
471
695
  const full = path.join(base, dir);
472
696
  let files = [];
473
697
  try {
474
- files = (await fs.readdir(full)).filter((f) => f.endsWith(".jsonl"));
698
+ files = (await fs2.readdir(full)).filter((f) => f.endsWith(".jsonl"));
475
699
  } catch {
476
700
  continue;
477
701
  }
@@ -479,21 +703,27 @@ async function readClaudeCode() {
479
703
  const file = path.join(full, f);
480
704
  let raw;
481
705
  try {
482
- raw = await fs.readFile(file, "utf-8");
706
+ raw = await fs2.readFile(file, "utf-8");
483
707
  } catch {
484
708
  continue;
485
709
  }
486
710
  const s = parseClaudeCode(raw, path.basename(f, ".jsonl"), file);
487
- if (s)
711
+ if (s) {
712
+ if (machine)
713
+ s.machine = machine;
488
714
  out.push(s);
715
+ }
489
716
  }
490
717
  }
491
718
  return out;
492
719
  }
720
+ async function readClaudeCode() {
721
+ return readClaudeCodeAt(claudeProjectsRoot(), null);
722
+ }
493
723
  async function walkJsonl(dir, acc) {
494
724
  let entries = [];
495
725
  try {
496
- entries = await fs.readdir(dir, { withFileTypes: true });
726
+ entries = await fs2.readdir(dir, { withFileTypes: true });
497
727
  } catch {
498
728
  return;
499
729
  }
@@ -505,8 +735,7 @@ async function walkJsonl(dir, acc) {
505
735
  acc.push(full);
506
736
  }
507
737
  }
508
- async function readCodex() {
509
- const base = codexSessionsRoot();
738
+ async function readCodexAt(base, machine) {
510
739
  if (!base)
511
740
  return [];
512
741
  const files = [];
@@ -515,17 +744,58 @@ async function readCodex() {
515
744
  for (const file of files) {
516
745
  let raw;
517
746
  try {
518
- raw = await fs.readFile(file, "utf-8");
747
+ raw = await fs2.readFile(file, "utf-8");
519
748
  } catch {
520
749
  continue;
521
750
  }
522
751
  const s = parseCodex(raw, file);
523
- if (s)
752
+ if (s) {
753
+ if (machine)
754
+ s.machine = machine;
524
755
  out.push(s);
756
+ }
525
757
  }
526
758
  return out;
527
759
  }
528
- var CURSOR_USER_QUERY_RE = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/;
760
+ async function readCodex() {
761
+ return readCodexAt(codexSessionsRoot(), null);
762
+ }
763
+ function machinesRoot() {
764
+ const d = process.env.POLYMATH_DATA_DIR;
765
+ if (!d || !d.trim())
766
+ return null;
767
+ return path.join(d, "machines");
768
+ }
769
+ async function readMachines(opts) {
770
+ const root = machinesRoot();
771
+ if (!root)
772
+ return [];
773
+ let labels = [];
774
+ try {
775
+ labels = await fs2.readdir(root, { withFileTypes: true });
776
+ } catch {
777
+ return [];
778
+ }
779
+ const out = [];
780
+ for (const ent of labels) {
781
+ if (!ent.isDirectory() || ent.name.startsWith("."))
782
+ continue;
783
+ const label = ent.name;
784
+ const b = path.join(root, label);
785
+ out.push(...await readClaudeCodeAt(path.join(b, ".claude", "projects"), label));
786
+ if (opts.codex !== false)
787
+ out.push(...await readCodexAt(path.join(b, ".codex", "sessions"), label));
788
+ if (opts.cursor !== false) {
789
+ out.push(...await readCursorAt({
790
+ workspace: path.join(b, ".cursor", "workspaceStorage"),
791
+ projects: path.join(b, ".cursor", "projects"),
792
+ globalDb: path.join(b, ".cursor", "globalStorage", "state.vscdb")
793
+ }, label));
794
+ }
795
+ }
796
+ return out;
797
+ }
798
+ var CURSOR_USER_QUERY_RE2 = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/;
529
799
  function fileUriToPath(uri) {
530
800
  if (typeof uri !== "string" || !uri)
531
801
  return null;
@@ -539,11 +809,11 @@ function fileUriToPath(uri) {
539
809
  function cursorSlug(folderPath) {
540
810
  return folderPath.replace(/^\//, "").replace(/[^A-Za-z0-9]+/g, "-").replace(/-+$/, "");
541
811
  }
542
- var NODE_SQLITE = "node:sqlite";
812
+ var NODE_SQLITE2 = "node:sqlite";
543
813
  async function openCursorDb(dbPath) {
544
814
  let mod;
545
815
  try {
546
- mod = await import(NODE_SQLITE);
816
+ mod = await import(NODE_SQLITE2);
547
817
  } catch {
548
818
  return null;
549
819
  }
@@ -581,14 +851,13 @@ async function openCursorDb(dbPath) {
581
851
  }
582
852
  };
583
853
  }
584
- async function readCursorWorkspaces() {
585
- const base = cursorWorkspaceRoot();
854
+ async function readCursorWorkspaces(base) {
586
855
  const out = /* @__PURE__ */ new Map();
587
856
  if (!base)
588
857
  return out;
589
858
  let dirs = [];
590
859
  try {
591
- dirs = await fs.readdir(base, { withFileTypes: true });
860
+ dirs = await fs2.readdir(base, { withFileTypes: true });
592
861
  } catch {
593
862
  return out;
594
863
  }
@@ -598,14 +867,14 @@ async function readCursorWorkspaces() {
598
867
  const wsDir = path.join(base, d.name);
599
868
  let folder = null;
600
869
  try {
601
- folder = fileUriToPath(JSON.parse(await fs.readFile(path.join(wsDir, "workspace.json"), "utf-8")).folder);
870
+ folder = fileUriToPath(JSON.parse(await fs2.readFile(path.join(wsDir, "workspace.json"), "utf-8")).folder);
602
871
  } catch {
603
872
  }
604
873
  if (!folder)
605
874
  continue;
606
875
  const dbFile = path.join(wsDir, "state.vscdb");
607
876
  try {
608
- await fs.access(dbFile);
877
+ await fs2.access(dbFile);
609
878
  } catch {
610
879
  continue;
611
880
  }
@@ -659,10 +928,40 @@ async function readCursorWorkspaces() {
659
928
  }
660
929
  return out;
661
930
  }
931
+ function rawSessionFromCursorComposer(c, file, cwd) {
932
+ const s = blank(c.composerId, "cursor", file);
933
+ s.cwd = cwd;
934
+ if (c.name)
935
+ pushDedup(s.titles, c.name);
936
+ for (const turn of c.turns) {
937
+ if (turn.type === 1) {
938
+ s.humanTurns++;
939
+ s.humanChars += turn.text.length;
940
+ if (!s.firstHuman)
941
+ s.firstHuman = turn.text.slice(0, 4e3);
942
+ s.events.push({ t: turn.t, k: "human" });
943
+ } else {
944
+ s.aiTurns++;
945
+ if (turn.model)
946
+ s.models[turn.model] = (s.models[turn.model] ?? 0) + 1;
947
+ if (turn.tool) {
948
+ s.toolCounts[turn.tool] = (s.toolCounts[turn.tool] ?? 0) + 1;
949
+ s.events.push({ t: turn.t, k: "tool" });
950
+ } else {
951
+ s.events.push({ t: turn.t, k: "ai" });
952
+ }
953
+ }
954
+ }
955
+ s.events.sort((a, b) => a.t - b.t);
956
+ const cl = classify(s);
957
+ s.klass = cl.klass;
958
+ s.klassReason = cl.reason;
959
+ return s;
960
+ }
662
961
  function parseCursorTranscript(raw, composerId, file, ws, fileMtime) {
663
962
  const s = blank(composerId, "cursor", file);
664
963
  s.cwd = ws.folder;
665
- let cursor = ws.generations.length ? ws.generations[0].t : fileMtime;
964
+ let cursor2 = ws.generations.length ? ws.generations[0].t : fileMtime;
666
965
  let humanIdx = 0;
667
966
  for (const lineRaw of raw.split("\n")) {
668
967
  const line = lineRaw.trim();
@@ -676,24 +975,24 @@ function parseCursorTranscript(raw, composerId, file, ws, fileMtime) {
676
975
  }
677
976
  const role = e.role;
678
977
  let text = extractText(e.message?.content).replace(SYSTEM_REMINDER_RE, "").trim();
679
- const m = text.match(CURSOR_USER_QUERY_RE);
978
+ const m = text.match(CURSOR_USER_QUERY_RE2);
680
979
  if (m)
681
980
  text = m[1].trim();
682
981
  if (!text)
683
982
  continue;
684
983
  if (role === "user") {
685
- const ts = ws.tsByText.get(text) ?? (humanIdx < ws.generations.length ? ws.generations[humanIdx].t : cursor + 1);
686
- cursor = Math.max(cursor, ts);
984
+ const ts = ws.tsByText.get(text) ?? (humanIdx < ws.generations.length ? ws.generations[humanIdx].t : cursor2 + 1);
985
+ cursor2 = Math.max(cursor2, ts);
687
986
  humanIdx++;
688
987
  s.humanTurns++;
689
988
  s.humanChars += text.length;
690
989
  if (!s.firstHuman)
691
990
  s.firstHuman = text.slice(0, 4e3);
692
- s.events.push({ t: cursor, k: "human" });
991
+ s.events.push({ t: cursor2, k: "human" });
693
992
  } else if (role === "assistant") {
694
- cursor += 1;
993
+ cursor2 += 1;
695
994
  s.aiTurns++;
696
- s.events.push({ t: cursor, k: "ai" });
995
+ s.events.push({ t: cursor2, k: "ai" });
697
996
  }
698
997
  }
699
998
  if (s.events.length === 0 && s.humanTurns === 0)
@@ -726,67 +1025,89 @@ function cursorWorkspaceSession(ws, idx) {
726
1025
  s.klassReason = c.reason;
727
1026
  return s;
728
1027
  }
729
- async function readCursor() {
730
- const workspaces = await readCursorWorkspaces();
1028
+ async function readCursorAt(roots, machine) {
1029
+ const workspaces = await readCursorWorkspaces(roots.workspace);
731
1030
  const out = [];
732
- const projects = cursorProjectsRoot();
1031
+ const add = (s) => {
1032
+ if (machine)
1033
+ s.machine = machine;
1034
+ out.push(s);
1035
+ };
1036
+ const projects = roots.projects;
733
1037
  const usedSlugs = /* @__PURE__ */ new Set();
734
- let dirs = [];
735
- if (projects) {
736
- try {
737
- dirs = await fs.readdir(projects, { withFileTypes: true });
738
- } catch {
739
- dirs = [];
740
- }
741
- }
742
- for (const d of dirs) {
743
- if (!d.isDirectory())
744
- continue;
745
- const slug = d.name;
746
- if (SKIP_DIR_RE.test(slug))
747
- continue;
748
- const transcriptsDir = path.join(projects, slug, "agent-transcripts");
749
- let composers = [];
750
- try {
751
- composers = await fs.readdir(transcriptsDir, { withFileTypes: true });
752
- } catch {
753
- continue;
1038
+ const dbPath = roots.globalDb;
1039
+ const db = dbPath ? await openCursorSqlite(dbPath) : null;
1040
+ try {
1041
+ let dirs = [];
1042
+ if (projects) {
1043
+ try {
1044
+ dirs = await fs2.readdir(projects, { withFileTypes: true });
1045
+ } catch {
1046
+ dirs = [];
1047
+ }
754
1048
  }
755
- const ws = workspaces.get(slug);
756
- if (!ws)
757
- continue;
758
- usedSlugs.add(slug);
759
- for (const c of composers) {
760
- if (!c.isDirectory())
1049
+ for (const d of dirs) {
1050
+ if (!d.isDirectory())
761
1051
  continue;
762
- const composerId = c.name;
763
- const jf = path.join(transcriptsDir, composerId, `${composerId}.jsonl`);
764
- let raw;
765
- let mtime = Date.now();
1052
+ const slug = d.name;
1053
+ if (SKIP_DIR_RE.test(slug))
1054
+ continue;
1055
+ const transcriptsDir = path.join(projects, slug, "agent-transcripts");
1056
+ let composers = [];
766
1057
  try {
767
- raw = await fs.readFile(jf, "utf-8");
1058
+ composers = await fs2.readdir(transcriptsDir, { withFileTypes: true });
768
1059
  } catch {
769
1060
  continue;
770
1061
  }
771
- try {
772
- mtime = (await fs.stat(jf)).mtimeMs;
773
- } catch {
1062
+ const ws = workspaces.get(slug);
1063
+ usedSlugs.add(slug);
1064
+ for (const c of composers) {
1065
+ if (!c.isDirectory())
1066
+ continue;
1067
+ const composerId = c.name;
1068
+ const jf = path.join(transcriptsDir, composerId, `${composerId}.jsonl`);
1069
+ const composer = db ? readCursorComposer(db, composerId) : null;
1070
+ if (composer) {
1071
+ const cwd = ws?.folder ?? composer.gitRepo;
1072
+ if (cwd) {
1073
+ add(rawSessionFromCursorComposer(composer, jf, cwd));
1074
+ continue;
1075
+ }
1076
+ }
1077
+ if (!ws)
1078
+ continue;
1079
+ let raw;
1080
+ let mtime = Date.now();
1081
+ try {
1082
+ raw = await fs2.readFile(jf, "utf-8");
1083
+ } catch {
1084
+ continue;
1085
+ }
1086
+ try {
1087
+ mtime = (await fs2.stat(jf)).mtimeMs;
1088
+ } catch {
1089
+ }
1090
+ const s = parseCursorTranscript(raw, composerId, jf, ws, mtime);
1091
+ if (s)
1092
+ add(s);
774
1093
  }
775
- const s = parseCursorTranscript(raw, composerId, jf, ws, mtime);
1094
+ }
1095
+ let i = 0;
1096
+ for (const ws of workspaces.values()) {
1097
+ if (usedSlugs.has(ws.slug))
1098
+ continue;
1099
+ const s = cursorWorkspaceSession(ws, i++);
776
1100
  if (s)
777
- out.push(s);
1101
+ add(s);
778
1102
  }
779
- }
780
- let i = 0;
781
- for (const ws of workspaces.values()) {
782
- if (usedSlugs.has(ws.slug))
783
- continue;
784
- const s = cursorWorkspaceSession(ws, i++);
785
- if (s)
786
- out.push(s);
1103
+ } finally {
1104
+ db?.close();
787
1105
  }
788
1106
  return out;
789
1107
  }
1108
+ async function readCursor() {
1109
+ return readCursorAt({ workspace: cursorWorkspaceRoot(), projects: cursorProjectsRoot(), globalDb: cursorGlobalDbPath() }, null);
1110
+ }
790
1111
  var FANOUT_PREFIX = 80;
791
1112
  var FANOUT_MIN_SESSIONS = 3;
792
1113
  function demoteTemplateFanouts(sessions) {
@@ -809,12 +1130,14 @@ function demoteTemplateFanouts(sessions) {
809
1130
  }
810
1131
  }
811
1132
  async function readAllSessions(opts = {}) {
812
- const [cc, cx, cr] = await Promise.all([
1133
+ const [cc, cx, cr, mm] = await Promise.all([
813
1134
  readClaudeCode(),
814
1135
  opts.codex === false ? Promise.resolve([]) : readCodex(),
815
- opts.cursor === false ? Promise.resolve([]) : readCursor()
1136
+ opts.cursor === false ? Promise.resolve([]) : readCursor(),
1137
+ readMachines({ codex: opts.codex, cursor: opts.cursor })
1138
+ // imported second-machine logs under the data home
816
1139
  ]);
817
- const all = [...cc, ...cx, ...cr];
1140
+ const all = [...cc, ...cx, ...cr, ...mm];
818
1141
  demoteTemplateFanouts(all);
819
1142
  return all;
820
1143
  }
@@ -848,6 +1171,16 @@ function localDate(ms2, tz) {
848
1171
  function localTime(ms2, tz) {
849
1172
  return new Date(ms2).toLocaleTimeString("en-GB", { timeZone: tz, hour12: false });
850
1173
  }
1174
+ function deriveMachine(cwd) {
1175
+ if (!cwd)
1176
+ return null;
1177
+ const m = /^\/(?:Users|home)\/([^/]+)/.exec(cwd);
1178
+ if (m)
1179
+ return m[1];
1180
+ if (/^\/root(\/|$)/.test(cwd))
1181
+ return "root";
1182
+ return null;
1183
+ }
851
1184
  function toSessionRecords(sessions, idleGapMin2 = DEFAULT_IDLE_GAP_MIN) {
852
1185
  const idleGapMs = idleGapMin2 * MIN;
853
1186
  return sessions.map((s) => {
@@ -861,6 +1194,9 @@ function toSessionRecords(sessions, idleGapMin2 = DEFAULT_IDLE_GAP_MIN) {
861
1194
  sessionId: s.sessionId,
862
1195
  source: s.source,
863
1196
  project: s.cwd,
1197
+ // an imported machine stamps its folder label on the RawSession; local
1198
+ // logs leave it undefined, so the tag falls back to the cwd home segment.
1199
+ machine: s.machine ?? deriveMachine(s.cwd),
864
1200
  gitBranch: s.gitBranch,
865
1201
  title: s.titles[s.titles.length - 1] || s.firstHuman.slice(0, 60) || "(untitled)",
866
1202
  titleHistory: s.titles,
@@ -1086,7 +1422,7 @@ function partition(sessions) {
1086
1422
  }
1087
1423
 
1088
1424
  // ../coding-core/dist/messages.js
1089
- import { promises as fs2 } from "fs";
1425
+ import { promises as fs3 } from "fs";
1090
1426
  function extractText2(content) {
1091
1427
  if (typeof content === "string")
1092
1428
  return content;
@@ -1113,15 +1449,35 @@ function extractUserMessagesCodex(raw) {
1113
1449
  }
1114
1450
  return out.sort((a, b) => a.t - b.t);
1115
1451
  }
1452
+ function extractUserMessagesCursor(c) {
1453
+ const out = [];
1454
+ let lastTs = null;
1455
+ for (const turn of c.turns) {
1456
+ const t = turn.t;
1457
+ const gapMs = Number.isFinite(t) && lastTs != null ? t - lastTs : NaN;
1458
+ if (Number.isFinite(t))
1459
+ lastTs = t;
1460
+ if (turn.type !== 1)
1461
+ continue;
1462
+ const text = humanTypedText(turn.text);
1463
+ if (text && Number.isFinite(t))
1464
+ out.push({ t, text, gapMs });
1465
+ }
1466
+ return out.sort((a, b) => a.t - b.t);
1467
+ }
1116
1468
  async function extractUserMessages(file) {
1117
1469
  let raw;
1118
1470
  try {
1119
- raw = await fs2.readFile(file, "utf-8");
1471
+ raw = await fs3.readFile(file, "utf-8");
1120
1472
  } catch {
1121
1473
  return [];
1122
1474
  }
1123
1475
  if (sniffCodexRaw(raw))
1124
1476
  return extractUserMessagesCodex(raw);
1477
+ if (sniffCursorRaw(raw)) {
1478
+ const c = await loadCursorComposer(file, raw, cursorDbPathForFile(file));
1479
+ return c ? extractUserMessagesCursor(c) : [];
1480
+ }
1125
1481
  const out = [];
1126
1482
  const userTexts = /* @__PURE__ */ new Set();
1127
1483
  const seenUuids = /* @__PURE__ */ new Set();
@@ -1402,7 +1758,7 @@ var TRANSCRIPT_LINE_RE = new RegExp(`^\\s*(${NOTE})((,| and) ${NOTE})*\\s*$|^\\s
1402
1758
 
1403
1759
  // ../../lib/calibration/index.ts
1404
1760
  var DEFAULT_CALIBRATION = {
1405
- version: "2026-07-16.2",
1761
+ version: "2026-07-21.1",
1406
1762
  // = RUBRIC_FINGERPRINT of the shipped npm rubric (packages/coding-analyzer/
1407
1763
  // src/grade/criteria.ts). A unit test fails loud when the rubric changes
1408
1764
  // without this being updated (and re-pushed to the server).
@@ -1498,13 +1854,14 @@ var DEFAULT_CALIBRATION = {
1498
1854
  },
1499
1855
  codingBenchmarks: {
1500
1856
  flow: {
1501
- // typical knowledge worker ≈ 35% of an 8h day focused; ~4h/day is the
1502
- // recognized deep-work ceiling (Newport) 50% most sit far below it.
1857
+ // typical knowledge worker ≈ 35% of an 8h day focused; the ~4h/day
1858
+ // deep-work ceiling (Newport) sits nearer ~40% of a real (9-10h) top
1859
+ // performer's day — 50% overstated it (owner call 2026-07-21).
1503
1860
  typicalPctOfDay: 0.35,
1504
- topPctOfDay: 0.5,
1861
+ topPctOfDay: 0.4,
1505
1862
  tag: "measured",
1506
- source: "RescueTime 2019 (~2h48m focused/day) \xB7 Cal Newport, Deep Work (~4h/day deep-work ceiling)",
1507
- line: "the best spend ~half the workday in true deep flow; ~4h/day is the human ceiling"
1863
+ source: "RescueTime 2019 (~2h48m focused/day) \xB7 Cal Newport, Deep Work (~4h/day deep-work ceiling against a 9-10h day)",
1864
+ line: "the best spend ~40% of the workday in true deep flow; ~4h/day is the human ceiling"
1508
1865
  },
1509
1866
  longestRun: {
1510
1867
  typicalHours: 0.67,
@@ -1560,15 +1917,16 @@ var GRADES = path2.join(CODING_DIR, "grades");
1560
1917
  var args = process.argv.slice(2);
1561
1918
  var codex = !args.includes("--no-codex");
1562
1919
  var claude = !args.includes("--no-claude");
1920
+ var cursor = !args.includes("--no-cursor");
1563
1921
  var idleGapMin = Number(args[args.indexOf("--idle") + 1]) || DEFAULT_IDLE_GAP_MIN;
1564
1922
  var OUT = CODING_DIR;
1565
1923
  function fmt(n) {
1566
1924
  return n.toLocaleString("en-US");
1567
1925
  }
1568
1926
  async function main() {
1569
- console.log(`[coding] reading raw logs (claude=${claude}, codex=${codex}, idleGap=${idleGapMin}m, tz=${DEFAULT_TZ}) \u2026`);
1927
+ console.log(`[coding] reading raw logs (claude=${claude}, codex=${codex}, cursor=${cursor}, idleGap=${idleGapMin}m, tz=${DEFAULT_TZ}) \u2026`);
1570
1928
  const t0 = Date.now();
1571
- let raw = await readAllSessions({ codex });
1929
+ let raw = await readAllSessions({ codex, cursor });
1572
1930
  if (!claude) {
1573
1931
  const dropped = raw.filter((s) => s.source === "claude-code").length;
1574
1932
  raw = raw.filter((s) => s.source !== "claude-code");
@@ -1593,12 +1951,12 @@ async function main() {
1593
1951
  const conc = buildConcurrency(canonRaw, canonRecords, { idleGapMin, tz: DEFAULT_TZ });
1594
1952
  const renames = detectRenames(canonRaw);
1595
1953
  const dayChart = buildDayChart(canonRaw, canonRecords, { idleGapMin, tz: DEFAULT_TZ });
1596
- await fs3.mkdir(OUT, { recursive: true });
1597
- await fs3.writeFile(path3.join(OUT, "sessions.json"), JSON.stringify(records, null, 2));
1598
- await fs3.writeFile(path3.join(OUT, "concurrency.json"), JSON.stringify(conc, null, 2));
1599
- await fs3.writeFile(path3.join(OUT, "renames.json"), JSON.stringify(renames, null, 2));
1600
- await fs3.writeFile(path3.join(OUT, "day-chart.json"), JSON.stringify(dayChart));
1601
- await fs3.writeFile(
1954
+ await fs4.mkdir(OUT, { recursive: true });
1955
+ await fs4.writeFile(path3.join(OUT, "sessions.json"), JSON.stringify(records, null, 2));
1956
+ await fs4.writeFile(path3.join(OUT, "concurrency.json"), JSON.stringify(conc, null, 2));
1957
+ await fs4.writeFile(path3.join(OUT, "renames.json"), JSON.stringify(renames, null, 2));
1958
+ await fs4.writeFile(path3.join(OUT, "day-chart.json"), JSON.stringify(dayChart));
1959
+ await fs4.writeFile(
1602
1960
  path3.join(OUT, "_classified.json"),
1603
1961
  JSON.stringify(raw.map((s) => ({ id: s.sessionId, source: s.source, klass: s.klass, reason: s.klassReason, cwd: s.cwd, firstHuman: s.firstHuman.slice(0, 120) })), null, 2)
1604
1962
  );