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.
- package/dist/cli.js +24620 -22073
- 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 +19600 -17706
- package/dist/pipeline/FOCUS-EXAMPLES.md +127 -0
- package/dist/pipeline/FOCUS-RUBRIC.md +418 -0
- package/dist/pipeline/coding-agglomerate.js +947 -79
- package/dist/pipeline/coding-aggregate.js +443 -23
- package/dist/pipeline/coding-build.js +569 -97
- package/dist/pipeline/coding-coaching.js +651 -101
- package/dist/pipeline/coding-day-digest.js +417 -36
- package/dist/pipeline/coding-delegation.js +564 -68
- package/dist/pipeline/coding-expertise.js +477 -59
- package/dist/pipeline/coding-flow.js +424 -15
- package/dist/pipeline/coding-focus.js +507 -51
- package/dist/pipeline/coding-frontier-detail.js +465 -47
- package/dist/pipeline/coding-frontier.js +7 -6
- package/dist/pipeline/coding-gap-dist.js +424 -15
- package/dist/pipeline/coding-gap.js +679 -128
- package/dist/pipeline/coding-grade.js +469 -42
- package/dist/pipeline/coding-growth.js +17041 -0
- package/dist/pipeline/coding-nutshell.js +168 -151
- package/dist/pipeline/coding-projects.js +7 -6
- package/dist/pipeline/coding-walkthrough.js +461 -37
- package/dist/pipeline/coding-workbrief.js +16387 -0
- package/dist/web/app.js +2095 -1396
- 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
|
|
|
@@ -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 && !
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
812
|
+
var NODE_SQLITE2 = "node:sqlite";
|
|
453
813
|
async function openCursorDb(dbPath) {
|
|
454
814
|
let mod;
|
|
455
815
|
try {
|
|
456
|
-
mod = await import(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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 :
|
|
596
|
-
|
|
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:
|
|
991
|
+
s.events.push({ t: cursor2, k: "human" });
|
|
603
992
|
} else if (role === "assistant") {
|
|
604
|
-
|
|
993
|
+
cursor2 += 1;
|
|
605
994
|
s.aiTurns++;
|
|
606
|
-
s.events.push({ t:
|
|
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
|
|
640
|
-
const workspaces = await readCursorWorkspaces();
|
|
1028
|
+
async function readCursorAt(roots, machine) {
|
|
1029
|
+
const workspaces = await readCursorWorkspaces(roots.workspace);
|
|
641
1030
|
const out = [];
|
|
642
|
-
const
|
|
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
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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
|
|
666
|
-
|
|
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
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
-
|
|
1058
|
+
composers = await fs2.readdir(transcriptsDir, { withFileTypes: true });
|
|
678
1059
|
} catch {
|
|
679
1060
|
continue;
|
|
680
1061
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1101
|
+
add(s);
|
|
688
1102
|
}
|
|
689
|
-
}
|
|
690
|
-
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
1394
|
-
//
|
|
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.
|
|
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 ~
|
|
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
|
-
|
|
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
|
|
1483
|
-
await
|
|
1484
|
-
await
|
|
1485
|
-
await
|
|
1486
|
-
await
|
|
1487
|
-
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(
|
|
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
|
);
|