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,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
 
@@ -42,12 +42,316 @@ function isHarnessEntry(e) {
42
42
  return e.isMeta === true || e.isCompactSummary === true;
43
43
  }
44
44
 
45
+ // ../coding-core/dist/codex.js
46
+ var SYSTEM_REMINDER_RE2 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
47
+ var CODEX_INJECTED_TEXT_RE = /^\s*(<(environment_context|user_instructions|ENVIRONMENT_CONTEXT|turn_aborted)\b|#\s*AGENTS\.md instructions\b)/;
48
+ function isCodexInjectedUserText(text) {
49
+ return CODEX_INJECTED_TEXT_RE.test(text);
50
+ }
51
+ function codexContentText(content) {
52
+ if (typeof content === "string")
53
+ return content;
54
+ if (!Array.isArray(content))
55
+ return "";
56
+ const parts = [];
57
+ for (const item of content) {
58
+ if (typeof item === "string") {
59
+ parts.push(item);
60
+ continue;
61
+ }
62
+ if (item && typeof item === "object") {
63
+ const it = item;
64
+ if (it.type === "input_text" || it.type === "output_text" || it.type === "text")
65
+ parts.push(it.text || "");
66
+ }
67
+ }
68
+ return parts.join("\n");
69
+ }
70
+ function sniffCodexRaw(raw) {
71
+ let checked = 0;
72
+ for (const lineRaw of raw.split("\n")) {
73
+ const line = lineRaw.trim();
74
+ if (!line)
75
+ continue;
76
+ if (checked++ >= 5)
77
+ break;
78
+ try {
79
+ const e = JSON.parse(line);
80
+ if (e.type === "session_meta" || e.type === "response_item" || e.type === "turn_context")
81
+ return true;
82
+ if (e.type === "user" || e.type === "assistant" || e.type === "summary" || e.type === "queue-operation")
83
+ return false;
84
+ } catch {
85
+ }
86
+ }
87
+ return false;
88
+ }
89
+ var EXIT_CODE_RE = /(?:Process exited with code|Exit code:)\s+(\d+)/;
90
+ function extractCodexEvents(raw) {
91
+ const out = [];
92
+ for (const lineRaw of raw.split("\n")) {
93
+ const line = lineRaw.trim();
94
+ if (!line)
95
+ continue;
96
+ let e;
97
+ try {
98
+ e = JSON.parse(line);
99
+ } catch {
100
+ continue;
101
+ }
102
+ if (e.type !== "response_item")
103
+ continue;
104
+ const p = e.payload ?? {};
105
+ const t = typeof e.timestamp === "string" ? Date.parse(e.timestamp) : NaN;
106
+ const ptype = p.type;
107
+ if (ptype === "message") {
108
+ const role = p.role;
109
+ const text = codexContentText(p.content).replace(SYSTEM_REMINDER_RE2, "").trim();
110
+ if (!text)
111
+ continue;
112
+ if (role === "user") {
113
+ if (isCodexInjectedUserText(text))
114
+ continue;
115
+ out.push({ kind: "user", t, text });
116
+ } else if (role === "assistant") {
117
+ out.push({ kind: "assistant", t, text });
118
+ }
119
+ continue;
120
+ }
121
+ if (ptype === "function_call" || ptype === "custom_tool_call" || ptype === "local_shell_call") {
122
+ const name = typeof p.name === "string" && p.name ? p.name : ptype === "local_shell_call" ? "shell" : String(ptype);
123
+ const input = typeof p.arguments === "string" ? p.arguments : typeof p.input === "string" ? p.input : "";
124
+ out.push({ kind: "tool", t, name, input });
125
+ continue;
126
+ }
127
+ if (ptype === "function_call_output" || ptype === "custom_tool_call_output") {
128
+ const output = typeof p.output === "string" ? p.output : "";
129
+ const m = EXIT_CODE_RE.exec(output.slice(0, 200));
130
+ out.push({ kind: "tool_output", t, output, isError: !!m && m[1] !== "0" });
131
+ }
132
+ }
133
+ return out;
134
+ }
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
+
45
345
  // ../coding-core/dist/raw.js
46
346
  var SOURCE_ROOT_ENV_VARS = [
47
347
  "CLAUDE_PROJECTS_DIR",
48
348
  "CODEX_SESSIONS_DIR",
49
349
  "CURSOR_WORKSPACE_DIR",
50
- "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"
51
355
  ];
52
356
  var anyRootOverridden = () => SOURCE_ROOT_ENV_VARS.some((v) => !!process.env[v]);
53
357
  function resolveSourceRoot(envVar, ...defaultSegments) {
@@ -62,6 +366,18 @@ var claudeProjectsRoot = () => resolveSourceRoot("CLAUDE_PROJECTS_DIR", ".claude
62
366
  var codexSessionsRoot = () => resolveSourceRoot("CODEX_SESSIONS_DIR", ".codex", "sessions");
63
367
  var cursorWorkspaceRoot = () => resolveSourceRoot("CURSOR_WORKSPACE_DIR", "Library", "Application Support", "Cursor", "User", "workspaceStorage");
64
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
+ }
65
381
  var SKIP_DIR_RE = /(private-var-folders|-T-ps-|--data(-|$)|-sources(-|$)|--conversations|-node-modules-|-tmp(-|$)|-polymath-rehearsal-|-polymath-overnight-)/;
66
382
  var AGENT_CWD_RE = /(\/\.data(\/|$)|\.polymath-society|\/sources(\/|$)|\/var\/folders\/|\/T\/ps-|\/timeline(\/|$)|onboarding-digest|engine-pilot|polymath-rehearsal|polymath-overnight)/;
67
383
  var PERSONA_RE = /^You are (a|an|the|\w+ing)\b/;
@@ -292,7 +608,6 @@ function codexText(content) {
292
608
  }
293
609
  return "";
294
610
  }
295
- var CODEX_INJECTED_RE = /^\s*<(environment_context|user_instructions)>/;
296
611
  function parseCodex(raw, file) {
297
612
  let sessionId = path.basename(file, ".jsonl");
298
613
  const s = blank(sessionId, "codex", file);
@@ -332,7 +647,7 @@ function parseCodex(raw, file) {
332
647
  const role = p.role;
333
648
  const text = codexText(p.content).replace(SYSTEM_REMINDER_RE, "").trim();
334
649
  if (role === "user") {
335
- if (text && !CODEX_INJECTED_RE.test(text)) {
650
+ if (text && !isCodexInjectedUserText(text)) {
336
651
  s.humanTurns++;
337
652
  s.humanChars += text.length;
338
653
  if (!s.firstHuman)
@@ -364,14 +679,13 @@ function parseCodex(raw, file) {
364
679
  s.klassReason = c.reason;
365
680
  return s;
366
681
  }
367
- async function readClaudeCode() {
368
- const base = claudeProjectsRoot();
682
+ async function readClaudeCodeAt(base, machine) {
369
683
  const out = [];
370
684
  if (!base)
371
685
  return out;
372
686
  let dirs = [];
373
687
  try {
374
- dirs = await fs.readdir(base);
688
+ dirs = await fs2.readdir(base);
375
689
  } catch {
376
690
  return out;
377
691
  }
@@ -381,7 +695,7 @@ async function readClaudeCode() {
381
695
  const full = path.join(base, dir);
382
696
  let files = [];
383
697
  try {
384
- files = (await fs.readdir(full)).filter((f) => f.endsWith(".jsonl"));
698
+ files = (await fs2.readdir(full)).filter((f) => f.endsWith(".jsonl"));
385
699
  } catch {
386
700
  continue;
387
701
  }
@@ -389,21 +703,27 @@ async function readClaudeCode() {
389
703
  const file = path.join(full, f);
390
704
  let raw;
391
705
  try {
392
- raw = await fs.readFile(file, "utf-8");
706
+ raw = await fs2.readFile(file, "utf-8");
393
707
  } catch {
394
708
  continue;
395
709
  }
396
710
  const s = parseClaudeCode(raw, path.basename(f, ".jsonl"), file);
397
- if (s)
711
+ if (s) {
712
+ if (machine)
713
+ s.machine = machine;
398
714
  out.push(s);
715
+ }
399
716
  }
400
717
  }
401
718
  return out;
402
719
  }
720
+ async function readClaudeCode() {
721
+ return readClaudeCodeAt(claudeProjectsRoot(), null);
722
+ }
403
723
  async function walkJsonl(dir, acc) {
404
724
  let entries = [];
405
725
  try {
406
- entries = await fs.readdir(dir, { withFileTypes: true });
726
+ entries = await fs2.readdir(dir, { withFileTypes: true });
407
727
  } catch {
408
728
  return;
409
729
  }
@@ -415,8 +735,7 @@ async function walkJsonl(dir, acc) {
415
735
  acc.push(full);
416
736
  }
417
737
  }
418
- async function readCodex() {
419
- const base = codexSessionsRoot();
738
+ async function readCodexAt(base, machine) {
420
739
  if (!base)
421
740
  return [];
422
741
  const files = [];
@@ -425,17 +744,58 @@ async function readCodex() {
425
744
  for (const file of files) {
426
745
  let raw;
427
746
  try {
428
- raw = await fs.readFile(file, "utf-8");
747
+ raw = await fs2.readFile(file, "utf-8");
429
748
  } catch {
430
749
  continue;
431
750
  }
432
751
  const s = parseCodex(raw, file);
433
- if (s)
752
+ if (s) {
753
+ if (machine)
754
+ s.machine = machine;
434
755
  out.push(s);
756
+ }
435
757
  }
436
758
  return out;
437
759
  }
438
- 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>/;
439
799
  function fileUriToPath(uri) {
440
800
  if (typeof uri !== "string" || !uri)
441
801
  return null;
@@ -449,11 +809,11 @@ function fileUriToPath(uri) {
449
809
  function cursorSlug(folderPath) {
450
810
  return folderPath.replace(/^\//, "").replace(/[^A-Za-z0-9]+/g, "-").replace(/-+$/, "");
451
811
  }
452
- var NODE_SQLITE = "node:sqlite";
812
+ var NODE_SQLITE2 = "node:sqlite";
453
813
  async function openCursorDb(dbPath) {
454
814
  let mod;
455
815
  try {
456
- mod = await import(NODE_SQLITE);
816
+ mod = await import(NODE_SQLITE2);
457
817
  } catch {
458
818
  return null;
459
819
  }
@@ -491,14 +851,13 @@ async function openCursorDb(dbPath) {
491
851
  }
492
852
  };
493
853
  }
494
- async function readCursorWorkspaces() {
495
- const base = cursorWorkspaceRoot();
854
+ async function readCursorWorkspaces(base) {
496
855
  const out = /* @__PURE__ */ new Map();
497
856
  if (!base)
498
857
  return out;
499
858
  let dirs = [];
500
859
  try {
501
- dirs = await fs.readdir(base, { withFileTypes: true });
860
+ dirs = await fs2.readdir(base, { withFileTypes: true });
502
861
  } catch {
503
862
  return out;
504
863
  }
@@ -508,14 +867,14 @@ async function readCursorWorkspaces() {
508
867
  const wsDir = path.join(base, d.name);
509
868
  let folder = null;
510
869
  try {
511
- 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);
512
871
  } catch {
513
872
  }
514
873
  if (!folder)
515
874
  continue;
516
875
  const dbFile = path.join(wsDir, "state.vscdb");
517
876
  try {
518
- await fs.access(dbFile);
877
+ await fs2.access(dbFile);
519
878
  } catch {
520
879
  continue;
521
880
  }
@@ -569,10 +928,40 @@ async function readCursorWorkspaces() {
569
928
  }
570
929
  return out;
571
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
+ }
572
961
  function parseCursorTranscript(raw, composerId, file, ws, fileMtime) {
573
962
  const s = blank(composerId, "cursor", file);
574
963
  s.cwd = ws.folder;
575
- let cursor = ws.generations.length ? ws.generations[0].t : fileMtime;
964
+ let cursor2 = ws.generations.length ? ws.generations[0].t : fileMtime;
576
965
  let humanIdx = 0;
577
966
  for (const lineRaw of raw.split("\n")) {
578
967
  const line = lineRaw.trim();
@@ -586,24 +975,24 @@ function parseCursorTranscript(raw, composerId, file, ws, fileMtime) {
586
975
  }
587
976
  const role = e.role;
588
977
  let text = extractText(e.message?.content).replace(SYSTEM_REMINDER_RE, "").trim();
589
- const m = text.match(CURSOR_USER_QUERY_RE);
978
+ const m = text.match(CURSOR_USER_QUERY_RE2);
590
979
  if (m)
591
980
  text = m[1].trim();
592
981
  if (!text)
593
982
  continue;
594
983
  if (role === "user") {
595
- const ts = ws.tsByText.get(text) ?? (humanIdx < ws.generations.length ? ws.generations[humanIdx].t : cursor + 1);
596
- 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);
597
986
  humanIdx++;
598
987
  s.humanTurns++;
599
988
  s.humanChars += text.length;
600
989
  if (!s.firstHuman)
601
990
  s.firstHuman = text.slice(0, 4e3);
602
- s.events.push({ t: cursor, k: "human" });
991
+ s.events.push({ t: cursor2, k: "human" });
603
992
  } else if (role === "assistant") {
604
- cursor += 1;
993
+ cursor2 += 1;
605
994
  s.aiTurns++;
606
- s.events.push({ t: cursor, k: "ai" });
995
+ s.events.push({ t: cursor2, k: "ai" });
607
996
  }
608
997
  }
609
998
  if (s.events.length === 0 && s.humanTurns === 0)
@@ -636,67 +1025,89 @@ function cursorWorkspaceSession(ws, idx) {
636
1025
  s.klassReason = c.reason;
637
1026
  return s;
638
1027
  }
639
- async function readCursor() {
640
- const workspaces = await readCursorWorkspaces();
1028
+ async function readCursorAt(roots, machine) {
1029
+ const workspaces = await readCursorWorkspaces(roots.workspace);
641
1030
  const out = [];
642
- const projects = cursorProjectsRoot();
1031
+ const add = (s) => {
1032
+ if (machine)
1033
+ s.machine = machine;
1034
+ out.push(s);
1035
+ };
1036
+ const projects = roots.projects;
643
1037
  const usedSlugs = /* @__PURE__ */ new Set();
644
- let dirs = [];
645
- if (projects) {
646
- try {
647
- dirs = await fs.readdir(projects, { withFileTypes: true });
648
- } catch {
649
- dirs = [];
650
- }
651
- }
652
- for (const d of dirs) {
653
- if (!d.isDirectory())
654
- continue;
655
- const slug = d.name;
656
- if (SKIP_DIR_RE.test(slug))
657
- continue;
658
- const transcriptsDir = path.join(projects, slug, "agent-transcripts");
659
- let composers = [];
660
- try {
661
- composers = await fs.readdir(transcriptsDir, { withFileTypes: true });
662
- } catch {
663
- 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
+ }
664
1048
  }
665
- const ws = workspaces.get(slug);
666
- if (!ws)
667
- continue;
668
- usedSlugs.add(slug);
669
- for (const c of composers) {
670
- if (!c.isDirectory())
1049
+ for (const d of dirs) {
1050
+ if (!d.isDirectory())
671
1051
  continue;
672
- const composerId = c.name;
673
- const jf = path.join(transcriptsDir, composerId, `${composerId}.jsonl`);
674
- let raw;
675
- 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 = [];
676
1057
  try {
677
- raw = await fs.readFile(jf, "utf-8");
1058
+ composers = await fs2.readdir(transcriptsDir, { withFileTypes: true });
678
1059
  } catch {
679
1060
  continue;
680
1061
  }
681
- try {
682
- mtime = (await fs.stat(jf)).mtimeMs;
683
- } 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);
684
1093
  }
685
- 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++);
686
1100
  if (s)
687
- out.push(s);
1101
+ add(s);
688
1102
  }
689
- }
690
- let i = 0;
691
- for (const ws of workspaces.values()) {
692
- if (usedSlugs.has(ws.slug))
693
- continue;
694
- const s = cursorWorkspaceSession(ws, i++);
695
- if (s)
696
- out.push(s);
1103
+ } finally {
1104
+ db?.close();
697
1105
  }
698
1106
  return out;
699
1107
  }
1108
+ async function readCursor() {
1109
+ return readCursorAt({ workspace: cursorWorkspaceRoot(), projects: cursorProjectsRoot(), globalDb: cursorGlobalDbPath() }, null);
1110
+ }
700
1111
  var FANOUT_PREFIX = 80;
701
1112
  var FANOUT_MIN_SESSIONS = 3;
702
1113
  function demoteTemplateFanouts(sessions) {
@@ -719,12 +1130,14 @@ function demoteTemplateFanouts(sessions) {
719
1130
  }
720
1131
  }
721
1132
  async function readAllSessions(opts = {}) {
722
- const [cc, cx, cr] = await Promise.all([
1133
+ const [cc, cx, cr, mm] = await Promise.all([
723
1134
  readClaudeCode(),
724
1135
  opts.codex === false ? Promise.resolve([]) : readCodex(),
725
- 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
726
1139
  ]);
727
- const all = [...cc, ...cx, ...cr];
1140
+ const all = [...cc, ...cx, ...cr, ...mm];
728
1141
  demoteTemplateFanouts(all);
729
1142
  return all;
730
1143
  }
@@ -758,6 +1171,16 @@ function localDate(ms2, tz) {
758
1171
  function localTime(ms2, tz) {
759
1172
  return new Date(ms2).toLocaleTimeString("en-GB", { timeZone: tz, hour12: false });
760
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
+ }
761
1184
  function toSessionRecords(sessions, idleGapMin2 = DEFAULT_IDLE_GAP_MIN) {
762
1185
  const idleGapMs = idleGapMin2 * MIN;
763
1186
  return sessions.map((s) => {
@@ -771,6 +1194,9 @@ function toSessionRecords(sessions, idleGapMin2 = DEFAULT_IDLE_GAP_MIN) {
771
1194
  sessionId: s.sessionId,
772
1195
  source: s.source,
773
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),
774
1200
  gitBranch: s.gitBranch,
775
1201
  title: s.titles[s.titles.length - 1] || s.firstHuman.slice(0, 60) || "(untitled)",
776
1202
  titleHistory: s.titles,
@@ -996,7 +1422,7 @@ function partition(sessions) {
996
1422
  }
997
1423
 
998
1424
  // ../coding-core/dist/messages.js
999
- import { promises as fs2 } from "fs";
1425
+ import { promises as fs3 } from "fs";
1000
1426
  function extractText2(content) {
1001
1427
  if (typeof content === "string")
1002
1428
  return content;
@@ -1007,13 +1433,51 @@ function extractText2(content) {
1007
1433
  function isToolResultOnly(content) {
1008
1434
  return Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
1009
1435
  }
1436
+ function extractUserMessagesCodex(raw) {
1437
+ const out = [];
1438
+ let lastTs = null;
1439
+ for (const ev of extractCodexEvents(raw)) {
1440
+ const t = ev.t;
1441
+ const gapMs = Number.isFinite(t) && lastTs != null ? t - lastTs : NaN;
1442
+ if (Number.isFinite(t))
1443
+ lastTs = t;
1444
+ if (ev.kind !== "user")
1445
+ continue;
1446
+ const text = humanTypedText(ev.text);
1447
+ if (text && Number.isFinite(t))
1448
+ out.push({ t, text, gapMs });
1449
+ }
1450
+ return out.sort((a, b) => a.t - b.t);
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
+ }
1010
1468
  async function extractUserMessages(file) {
1011
1469
  let raw;
1012
1470
  try {
1013
- raw = await fs2.readFile(file, "utf-8");
1471
+ raw = await fs3.readFile(file, "utf-8");
1014
1472
  } catch {
1015
1473
  return [];
1016
1474
  }
1475
+ if (sniffCodexRaw(raw))
1476
+ return extractUserMessagesCodex(raw);
1477
+ if (sniffCursorRaw(raw)) {
1478
+ const c = await loadCursorComposer(file, raw, cursorDbPathForFile(file));
1479
+ return c ? extractUserMessagesCursor(c) : [];
1480
+ }
1017
1481
  const out = [];
1018
1482
  const userTexts = /* @__PURE__ */ new Set();
1019
1483
  const seenUuids = /* @__PURE__ */ new Set();
@@ -1294,7 +1758,7 @@ var TRANSCRIPT_LINE_RE = new RegExp(`^\\s*(${NOTE})((,| and) ${NOTE})*\\s*$|^\\s
1294
1758
 
1295
1759
  // ../../lib/calibration/index.ts
1296
1760
  var DEFAULT_CALIBRATION = {
1297
- version: "2026-07-16.2",
1761
+ version: "2026-07-21.1",
1298
1762
  // = RUBRIC_FINGERPRINT of the shipped npm rubric (packages/coding-analyzer/
1299
1763
  // src/grade/criteria.ts). A unit test fails loud when the rubric changes
1300
1764
  // without this being updated (and re-pushed to the server).
@@ -1390,13 +1854,14 @@ var DEFAULT_CALIBRATION = {
1390
1854
  },
1391
1855
  codingBenchmarks: {
1392
1856
  flow: {
1393
- // typical knowledge worker ≈ 35% of an 8h day focused; ~4h/day is the
1394
- // 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).
1395
1860
  typicalPctOfDay: 0.35,
1396
- topPctOfDay: 0.5,
1861
+ topPctOfDay: 0.4,
1397
1862
  tag: "measured",
1398
- source: "RescueTime 2019 (~2h48m focused/day) \xB7 Cal Newport, Deep Work (~4h/day deep-work ceiling)",
1399
- 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"
1400
1865
  },
1401
1866
  longestRun: {
1402
1867
  typicalHours: 0.67,
@@ -1451,15 +1916,22 @@ var GRADES = path2.join(CODING_DIR, "grades");
1451
1916
  // ../../scripts/coding-build.mts
1452
1917
  var args = process.argv.slice(2);
1453
1918
  var codex = !args.includes("--no-codex");
1919
+ var claude = !args.includes("--no-claude");
1920
+ var cursor = !args.includes("--no-cursor");
1454
1921
  var idleGapMin = Number(args[args.indexOf("--idle") + 1]) || DEFAULT_IDLE_GAP_MIN;
1455
1922
  var OUT = CODING_DIR;
1456
1923
  function fmt(n) {
1457
1924
  return n.toLocaleString("en-US");
1458
1925
  }
1459
1926
  async function main() {
1460
- console.log(`[coding] reading raw logs (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`);
1461
1928
  const t0 = Date.now();
1462
- const raw = await readAllSessions({ codex });
1929
+ let raw = await readAllSessions({ codex, cursor });
1930
+ if (!claude) {
1931
+ const dropped = raw.filter((s) => s.source === "claude-code").length;
1932
+ raw = raw.filter((s) => s.source !== "claude-code");
1933
+ console.log(`[coding] \u26D4 Claude Code EXCLUDED (--no-claude, consent) \u2014 ${dropped} session file(s) dropped before any analysis.`);
1934
+ }
1463
1935
  console.log(`[coding] parsed ${fmt(raw.length)} sessions in ${((Date.now() - t0) / 1e3).toFixed(1)}s`);
1464
1936
  const interactive = raw.filter((s) => s.klass === "interactive");
1465
1937
  const agent = raw.filter((s) => s.klass === "agent");
@@ -1479,12 +1951,12 @@ async function main() {
1479
1951
  const conc = buildConcurrency(canonRaw, canonRecords, { idleGapMin, tz: DEFAULT_TZ });
1480
1952
  const renames = detectRenames(canonRaw);
1481
1953
  const dayChart = buildDayChart(canonRaw, canonRecords, { idleGapMin, tz: DEFAULT_TZ });
1482
- await fs3.mkdir(OUT, { recursive: true });
1483
- await fs3.writeFile(path3.join(OUT, "sessions.json"), JSON.stringify(records, null, 2));
1484
- await fs3.writeFile(path3.join(OUT, "concurrency.json"), JSON.stringify(conc, null, 2));
1485
- await fs3.writeFile(path3.join(OUT, "renames.json"), JSON.stringify(renames, null, 2));
1486
- await fs3.writeFile(path3.join(OUT, "day-chart.json"), JSON.stringify(dayChart));
1487
- 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(
1488
1960
  path3.join(OUT, "_classified.json"),
1489
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)
1490
1962
  );