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,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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
812
|
+
var NODE_SQLITE2 = "node:sqlite";
|
|
543
813
|
async function openCursorDb(dbPath) {
|
|
544
814
|
let mod;
|
|
545
815
|
try {
|
|
546
|
-
mod = await import(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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 :
|
|
686
|
-
|
|
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:
|
|
991
|
+
s.events.push({ t: cursor2, k: "human" });
|
|
693
992
|
} else if (role === "assistant") {
|
|
694
|
-
|
|
993
|
+
cursor2 += 1;
|
|
695
994
|
s.aiTurns++;
|
|
696
|
-
s.events.push({ t:
|
|
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
|
|
730
|
-
const workspaces = await readCursorWorkspaces();
|
|
1028
|
+
async function readCursorAt(roots, machine) {
|
|
1029
|
+
const workspaces = await readCursorWorkspaces(roots.workspace);
|
|
731
1030
|
const out = [];
|
|
732
|
-
const
|
|
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
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
|
756
|
-
|
|
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
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
|
|
1058
|
+
composers = await fs2.readdir(transcriptsDir, { withFileTypes: true });
|
|
768
1059
|
} catch {
|
|
769
1060
|
continue;
|
|
770
1061
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1101
|
+
add(s);
|
|
778
1102
|
}
|
|
779
|
-
}
|
|
780
|
-
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
1502
|
-
//
|
|
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.
|
|
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 ~
|
|
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
|
|
1597
|
-
await
|
|
1598
|
-
await
|
|
1599
|
-
await
|
|
1600
|
-
await
|
|
1601
|
-
await
|
|
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
|
);
|