polymath-society 0.2.4
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/LICENSE +52 -0
- package/README.md +311 -0
- package/dist/DATA-MAP.md +109 -0
- package/dist/cli.js +24136 -0
- package/dist/engine/closing-rubric.md +50 -0
- package/dist/engine/exp-allfacets.js +16832 -0
- package/dist/engine/exp-person.js +16922 -0
- package/dist/engine/exp-pipeline.js +16700 -0
- package/dist/engine/feel-seen-rubric.md +192 -0
- package/dist/engine/ingest-export.js +1423 -0
- package/dist/engine/peak-demos.js +16752 -0
- package/dist/engine/person-dimension-summary.js +16744 -0
- package/dist/engine/person-facet-lines.js +16784 -0
- package/dist/engine/person-headline.js +16733 -0
- package/dist/engine/person-report.js +16845 -0
- package/dist/engine/person-self-image.js +16689 -0
- package/dist/engine/public-report-guidelines.md +80 -0
- package/dist/engine/public-report.js +17284 -0
- package/dist/engine/run-analysis.js +16035 -0
- package/dist/engine/run-tagger.js +16092 -0
- package/dist/engine/top-companies.md +41 -0
- package/dist/engine/writing-well.md +56 -0
- package/dist/index.js +22021 -0
- package/dist/pipeline/TECHNIQUES.md +406 -0
- package/dist/pipeline/WRITING.md +48 -0
- package/dist/pipeline/coding-agglomerate.js +15876 -0
- package/dist/pipeline/coding-aggregate.js +440 -0
- package/dist/pipeline/coding-build.js +1168 -0
- package/dist/pipeline/coding-coaching.js +16893 -0
- package/dist/pipeline/coding-day-digest.js +15869 -0
- package/dist/pipeline/coding-delegation.js +16292 -0
- package/dist/pipeline/coding-expertise.js +15925 -0
- package/dist/pipeline/coding-flow.js +313 -0
- package/dist/pipeline/coding-frontier-detail.js +15878 -0
- package/dist/pipeline/coding-frontier.js +51 -0
- package/dist/pipeline/coding-gap-dist.js +293 -0
- package/dist/pipeline/coding-gap.js +17108 -0
- package/dist/pipeline/coding-grade.js +16195 -0
- package/dist/pipeline/coding-nutshell.js +15725 -0
- package/dist/pipeline/coding-projects.js +104 -0
- package/dist/pipeline/coding-walkthrough.js +15947 -0
- package/dist/web/app.js +12024 -0
- package/dist/web/index.html +1 -0
- package/dist/web/styles.css +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,1423 @@
|
|
|
1
|
+
import{createRequire as __cr}from'module';const require=__cr(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// ../../lib/onboarding/retrieval/manual.ts
|
|
4
|
+
import { promises as fs8 } from "fs";
|
|
5
|
+
import os4 from "os";
|
|
6
|
+
import path10 from "path";
|
|
7
|
+
|
|
8
|
+
// ../../lib/onboarding/state.ts
|
|
9
|
+
import { promises as fs2 } from "fs";
|
|
10
|
+
import path2 from "path";
|
|
11
|
+
import crypto from "crypto";
|
|
12
|
+
|
|
13
|
+
// ../../lib/store.ts
|
|
14
|
+
import { promises as fs } from "fs";
|
|
15
|
+
import path from "path";
|
|
16
|
+
var DATA_DIR = process.env.POLYMATH_DATA_DIR ? path.resolve(process.env.POLYMATH_DATA_DIR) : path.join(process.cwd(), ".data");
|
|
17
|
+
var IMPORTED_DIR = path.join(DATA_DIR, "imported");
|
|
18
|
+
var PROFILE_FILE = path.join(DATA_DIR, "profile.json");
|
|
19
|
+
var ENTRIES_FILE = path.join(DATA_DIR, "entries.json");
|
|
20
|
+
var CONNECTIONS_FILE = path.join(DATA_DIR, "connections.json");
|
|
21
|
+
var EVAL_FILE = path.join(DATA_DIR, "evaluation.json");
|
|
22
|
+
var ENGINE_FILE = path.join(DATA_DIR, "engine.json");
|
|
23
|
+
async function ensureDir(dir) {
|
|
24
|
+
await fs.mkdir(dir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
async function readJSON(file2, fallback) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(await fs.readFile(file2, "utf-8"));
|
|
29
|
+
} catch {
|
|
30
|
+
return fallback;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function writeJSON(file2, data) {
|
|
34
|
+
await ensureDir(path.dirname(file2));
|
|
35
|
+
await fs.writeFile(file2, JSON.stringify(data, null, 2), "utf-8");
|
|
36
|
+
}
|
|
37
|
+
async function getProfile() {
|
|
38
|
+
return readJSON(PROFILE_FILE, null);
|
|
39
|
+
}
|
|
40
|
+
async function getEntries() {
|
|
41
|
+
return readJSON(ENTRIES_FILE, []);
|
|
42
|
+
}
|
|
43
|
+
async function getConnections() {
|
|
44
|
+
return readJSON(CONNECTIONS_FILE, {});
|
|
45
|
+
}
|
|
46
|
+
async function setConnection(id, patch) {
|
|
47
|
+
const conns = await getConnections();
|
|
48
|
+
const existing = conns[id] || { id, connected: false, lastSync: null, eventCount: 0, available: true };
|
|
49
|
+
const next = { ...existing, ...patch, id };
|
|
50
|
+
conns[id] = next;
|
|
51
|
+
await writeJSON(CONNECTIONS_FILE, conns);
|
|
52
|
+
return next;
|
|
53
|
+
}
|
|
54
|
+
async function saveImported(id, events, fileKey) {
|
|
55
|
+
await writeJSON(path.join(IMPORTED_DIR, `${fileKey ?? id}.json`), events);
|
|
56
|
+
}
|
|
57
|
+
async function getImported(id, fileKey) {
|
|
58
|
+
return readJSON(path.join(IMPORTED_DIR, `${fileKey ?? id}.json`), []);
|
|
59
|
+
}
|
|
60
|
+
async function removeImported(fileKey) {
|
|
61
|
+
await fs.unlink(path.join(IMPORTED_DIR, `${fileKey}.json`)).catch(() => {
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async function recordImport(id, imp) {
|
|
65
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
66
|
+
if (!imp.account) {
|
|
67
|
+
await saveImported(id, imp.events);
|
|
68
|
+
return setConnection(id, {
|
|
69
|
+
// GUARD: "connected" means content actually came in. A Notion export once
|
|
70
|
+
// imported 0 notes and still showed "Synced" — an empty import must read
|
|
71
|
+
// as NOT connected so the failure is visible, never as a green pill.
|
|
72
|
+
connected: imp.events.length > 0,
|
|
73
|
+
lastSync: now,
|
|
74
|
+
eventCount: imp.events.length,
|
|
75
|
+
files: imp.files,
|
|
76
|
+
root: imp.root,
|
|
77
|
+
...imp.archive ? { archive: imp.archive } : {},
|
|
78
|
+
...imp.conversations != null ? { conversations: imp.conversations } : {},
|
|
79
|
+
available: true
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
await saveImported(id, imp.events, `${id}.acct-${imp.account.id}`);
|
|
83
|
+
const cur = (await getConnections())[id];
|
|
84
|
+
let accounts = [...cur?.accounts ?? []];
|
|
85
|
+
const legacy = await getImported(id);
|
|
86
|
+
if (legacy.length) {
|
|
87
|
+
const ids = new Set(imp.events.map((e) => e.id));
|
|
88
|
+
const overlap = legacy.reduce((n, e) => n + (ids.has(e.id) ? 1 : 0), 0);
|
|
89
|
+
if (overlap >= 0.25 * Math.min(legacy.length, imp.events.length)) {
|
|
90
|
+
await removeImported(id);
|
|
91
|
+
accounts = accounts.filter((a) => a.id !== "legacy");
|
|
92
|
+
} else if (!accounts.some((a) => a.id === "legacy")) {
|
|
93
|
+
accounts.push({
|
|
94
|
+
id: "legacy",
|
|
95
|
+
label: "earlier import",
|
|
96
|
+
eventCount: legacy.length,
|
|
97
|
+
lastSync: cur?.lastSync ?? null,
|
|
98
|
+
...cur?.conversations != null ? { conversations: cur.conversations } : {},
|
|
99
|
+
...cur?.archive ? { archive: cur.archive } : {},
|
|
100
|
+
files: cur?.files,
|
|
101
|
+
root: cur?.root
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
accounts = accounts.filter((a) => a.id !== "legacy");
|
|
106
|
+
}
|
|
107
|
+
const entry = {
|
|
108
|
+
id: imp.account.id,
|
|
109
|
+
label: imp.account.label,
|
|
110
|
+
eventCount: imp.events.length,
|
|
111
|
+
...imp.conversations != null ? { conversations: imp.conversations } : {},
|
|
112
|
+
...imp.archive ? { archive: imp.archive } : {},
|
|
113
|
+
lastSync: now,
|
|
114
|
+
files: imp.files,
|
|
115
|
+
root: imp.root
|
|
116
|
+
};
|
|
117
|
+
accounts = [...accounts.filter((a) => a.id !== entry.id), entry];
|
|
118
|
+
return setConnection(id, {
|
|
119
|
+
// Same guard as the single-slot path: no content, no "connected".
|
|
120
|
+
connected: accounts.some((a) => a.eventCount > 0),
|
|
121
|
+
lastSync: now,
|
|
122
|
+
eventCount: accounts.reduce((s, a) => s + a.eventCount, 0),
|
|
123
|
+
conversations: accounts.reduce((s, a) => s + (a.conversations ?? 0), 0),
|
|
124
|
+
files: accounts.flatMap((a) => a.files ?? []),
|
|
125
|
+
root: imp.root,
|
|
126
|
+
...imp.archive ? { archive: imp.archive } : {},
|
|
127
|
+
accounts,
|
|
128
|
+
available: true
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async function getAllImported() {
|
|
132
|
+
let names = [];
|
|
133
|
+
try {
|
|
134
|
+
names = (await fs.readdir(IMPORTED_DIR)).filter((n) => n.endsWith(".json"));
|
|
135
|
+
} catch {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
const all = [];
|
|
139
|
+
for (const n of names) {
|
|
140
|
+
all.push(...await readJSON(path.join(IMPORTED_DIR, n), []));
|
|
141
|
+
}
|
|
142
|
+
return all;
|
|
143
|
+
}
|
|
144
|
+
async function saveEvaluation(e) {
|
|
145
|
+
await writeJSON(EVAL_FILE, e);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ../../lib/onboarding/state.ts
|
|
149
|
+
var ONBOARDING_DIR = path2.join(DATA_DIR, "onboarding");
|
|
150
|
+
var STATE_FILE = path2.join(ONBOARDING_DIR, "state.json");
|
|
151
|
+
var DIGEST_DIR = path2.join(ONBOARDING_DIR, "digest");
|
|
152
|
+
var EXPORTS_DIR = path2.join(ONBOARDING_DIR, "exports");
|
|
153
|
+
function empty() {
|
|
154
|
+
return {
|
|
155
|
+
glimpse: null,
|
|
156
|
+
enrichment: null,
|
|
157
|
+
detected: [],
|
|
158
|
+
jobs: [],
|
|
159
|
+
computerPermissionGrantedAt: null,
|
|
160
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async function getState() {
|
|
164
|
+
try {
|
|
165
|
+
return JSON.parse(await fs2.readFile(STATE_FILE, "utf-8"));
|
|
166
|
+
} catch {
|
|
167
|
+
return empty();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function writeState(s) {
|
|
171
|
+
s.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
172
|
+
await fs2.mkdir(ONBOARDING_DIR, { recursive: true });
|
|
173
|
+
await fs2.writeFile(STATE_FILE, JSON.stringify(s, null, 2), "utf-8");
|
|
174
|
+
return s;
|
|
175
|
+
}
|
|
176
|
+
function newJobId() {
|
|
177
|
+
return crypto.randomUUID();
|
|
178
|
+
}
|
|
179
|
+
async function upsertJob(job) {
|
|
180
|
+
const s = await getState();
|
|
181
|
+
const i = s.jobs.findIndex((j) => j.id === job.id);
|
|
182
|
+
job.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
183
|
+
if (i >= 0) s.jobs[i] = job;
|
|
184
|
+
else s.jobs.push(job);
|
|
185
|
+
await writeState(s);
|
|
186
|
+
return job;
|
|
187
|
+
}
|
|
188
|
+
async function patchJob(id, patch) {
|
|
189
|
+
const s = await getState();
|
|
190
|
+
const i = s.jobs.findIndex((j) => j.id === id);
|
|
191
|
+
if (i < 0) return null;
|
|
192
|
+
s.jobs[i] = { ...s.jobs[i], ...patch, id, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
193
|
+
await writeState(s);
|
|
194
|
+
return s.jobs[i];
|
|
195
|
+
}
|
|
196
|
+
async function createJob(source) {
|
|
197
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
198
|
+
const job = {
|
|
199
|
+
id: newJobId(),
|
|
200
|
+
source,
|
|
201
|
+
stage: "requested",
|
|
202
|
+
requestedAt: now,
|
|
203
|
+
updatedAt: now
|
|
204
|
+
};
|
|
205
|
+
return upsertJob(job);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ../../lib/connectors/fileImport.ts
|
|
209
|
+
import { promises as fs4 } from "fs";
|
|
210
|
+
import path5 from "path";
|
|
211
|
+
import os from "os";
|
|
212
|
+
import { execFile } from "child_process";
|
|
213
|
+
import { promisify } from "util";
|
|
214
|
+
|
|
215
|
+
// ../../lib/connectors/util.ts
|
|
216
|
+
import { promises as fs3 } from "fs";
|
|
217
|
+
import path3 from "path";
|
|
218
|
+
import crypto2 from "crypto";
|
|
219
|
+
function sha16(obj) {
|
|
220
|
+
return crypto2.createHash("sha256").update(typeof obj === "string" ? obj : JSON.stringify(obj)).digest("hex").slice(0, 16);
|
|
221
|
+
}
|
|
222
|
+
function isExcluded(file2, excluded) {
|
|
223
|
+
if (!excluded || excluded.length === 0) return false;
|
|
224
|
+
for (const ex of excluded) {
|
|
225
|
+
if (file2 === ex) return true;
|
|
226
|
+
if (file2.startsWith(ex.endsWith(path3.sep) ? ex : ex + path3.sep)) return true;
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
async function walk(dir, ext) {
|
|
231
|
+
let out = [];
|
|
232
|
+
let entries;
|
|
233
|
+
try {
|
|
234
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
235
|
+
} catch {
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
for (const e of entries) {
|
|
239
|
+
const full = path3.join(dir, e.name);
|
|
240
|
+
if (e.isDirectory()) {
|
|
241
|
+
out = out.concat(await walk(full, ext));
|
|
242
|
+
} else if (e.isFile() && e.name.endsWith(ext)) {
|
|
243
|
+
out.push(full);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ../../lib/agents/shared/worklist.ts
|
|
250
|
+
import path4 from "path";
|
|
251
|
+
var MONTHS = {
|
|
252
|
+
jan: 1,
|
|
253
|
+
feb: 2,
|
|
254
|
+
mar: 3,
|
|
255
|
+
apr: 4,
|
|
256
|
+
may: 5,
|
|
257
|
+
jun: 6,
|
|
258
|
+
jul: 7,
|
|
259
|
+
aug: 8,
|
|
260
|
+
sep: 9,
|
|
261
|
+
oct: 10,
|
|
262
|
+
nov: 11,
|
|
263
|
+
dec: 12
|
|
264
|
+
};
|
|
265
|
+
function inferDate(relPath) {
|
|
266
|
+
const segs = relPath.split(path4.sep);
|
|
267
|
+
let year = 0;
|
|
268
|
+
let month = 0;
|
|
269
|
+
let day = 0;
|
|
270
|
+
for (const seg of segs) {
|
|
271
|
+
const y = seg.match(/(20\d{2})/);
|
|
272
|
+
if (y) year = Math.max(year, Number(y[1]));
|
|
273
|
+
const mNum = seg.match(/^(\d{1,2})\s*[-–]\s*[A-Za-z]/);
|
|
274
|
+
if (mNum) {
|
|
275
|
+
const m = Number(mNum[1]);
|
|
276
|
+
if (m >= 1 && m <= 12) month = m;
|
|
277
|
+
}
|
|
278
|
+
const mName = seg.toLowerCase().match(/\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/);
|
|
279
|
+
if (mName && !month) month = MONTHS[mName[1]];
|
|
280
|
+
}
|
|
281
|
+
const last = segs[segs.length - 1].replace(/\.md$/i, "");
|
|
282
|
+
const pair = last.match(/^(\d{1,2})\s*[-–]\s*(\d{1,2})/);
|
|
283
|
+
if (pair) {
|
|
284
|
+
const a = Number(pair[1]);
|
|
285
|
+
const b = Number(pair[2]);
|
|
286
|
+
if (month && (a === month || b === month)) {
|
|
287
|
+
day = a === month ? b : a;
|
|
288
|
+
} else if (!month) {
|
|
289
|
+
if (a >= 1 && a <= 12) {
|
|
290
|
+
month = a;
|
|
291
|
+
day = b;
|
|
292
|
+
} else if (b >= 1 && b <= 12) {
|
|
293
|
+
month = b;
|
|
294
|
+
day = a;
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
day = b;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (!year) return { ym: "", date: "" };
|
|
301
|
+
const mm = month ? String(month).padStart(2, "0") : "";
|
|
302
|
+
const ym = mm ? `${year}-${mm}` : `${year}`;
|
|
303
|
+
const date = mm && day ? `${ym}-${String(day).padStart(2, "0")}` : ym;
|
|
304
|
+
return { ym, date };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ../../lib/connectors/fileImport.ts
|
|
308
|
+
var run = promisify(execFile);
|
|
309
|
+
async function extractZip(zipPath, dest) {
|
|
310
|
+
try {
|
|
311
|
+
await run("tar", ["-xf", zipPath, "-C", dest], { maxBuffer: 256 * 1024 * 1024 });
|
|
312
|
+
} catch (tarErr) {
|
|
313
|
+
console.warn(`[fileImport] tar -xf failed on ${path5.basename(zipPath)} (${tarErr.message.slice(0, 120)}) \u2014 falling back to unzip`);
|
|
314
|
+
await new Promise((resolve, reject) => {
|
|
315
|
+
execFile("unzip", ["-o", "-q", zipPath, "-d", dest], { maxBuffer: 256 * 1024 * 1024 }, (e) => e ? reject(e) : resolve()).stdin?.end();
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function unzipTo(zipPath, dest) {
|
|
320
|
+
await fs4.rm(dest, { recursive: true, force: true });
|
|
321
|
+
await fs4.mkdir(dest, { recursive: true });
|
|
322
|
+
await extractZip(zipPath, dest);
|
|
323
|
+
await expandNestedZips(dest);
|
|
324
|
+
}
|
|
325
|
+
async function expandNestedZips(dest, depth = 0) {
|
|
326
|
+
if (depth >= 3) return;
|
|
327
|
+
const zips = await walk(dest, ".zip");
|
|
328
|
+
for (const z of zips) {
|
|
329
|
+
const sub = z.replace(/\.zip$/i, "");
|
|
330
|
+
await fs4.mkdir(sub, { recursive: true });
|
|
331
|
+
await extractZip(z, sub);
|
|
332
|
+
await fs4.rm(z, { force: true });
|
|
333
|
+
}
|
|
334
|
+
if (zips.length) await expandNestedZips(dest, depth + 1);
|
|
335
|
+
}
|
|
336
|
+
function htmlToText(html) {
|
|
337
|
+
return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'").replace(/"/g, '"').replace(/\s+/g, " ").trim();
|
|
338
|
+
}
|
|
339
|
+
async function readAccountIdentity(source, root) {
|
|
340
|
+
try {
|
|
341
|
+
if (source === "claude") {
|
|
342
|
+
const users = JSON.parse(await fs4.readFile(path5.join(root, "users.json"), "utf-8"));
|
|
343
|
+
const email = Array.isArray(users) ? users[0]?.email_address : void 0;
|
|
344
|
+
if (email) return { id: sha16({ email }).slice(0, 8), label: email };
|
|
345
|
+
}
|
|
346
|
+
if (source === "chatgpt") {
|
|
347
|
+
const user = JSON.parse(await fs4.readFile(path5.join(root, "user.json"), "utf-8"));
|
|
348
|
+
if (user?.email) return { id: sha16({ email: user.email }).slice(0, 8), label: user.email };
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
return void 0;
|
|
353
|
+
}
|
|
354
|
+
async function detectChatExport(root) {
|
|
355
|
+
try {
|
|
356
|
+
const users = JSON.parse(await fs4.readFile(path5.join(root, "users.json"), "utf-8"));
|
|
357
|
+
if (Array.isArray(users) && users[0]?.email_address) return "claude";
|
|
358
|
+
} catch {
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
const user = JSON.parse(await fs4.readFile(path5.join(root, "user.json"), "utf-8"));
|
|
362
|
+
if (user?.email || user?.chatgpt_plus_user !== void 0) return "chatgpt";
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
const [shard] = await findConversationsJsons(root);
|
|
366
|
+
if (shard) {
|
|
367
|
+
try {
|
|
368
|
+
const data = JSON.parse(await fs4.readFile(shard, "utf-8"));
|
|
369
|
+
const first = Array.isArray(data) ? data[0] : null;
|
|
370
|
+
if (first?.mapping) return "chatgpt";
|
|
371
|
+
if (first?.chat_messages) return "claude";
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
async function importNotes(source, root, exclude) {
|
|
378
|
+
const events = [];
|
|
379
|
+
const files = [];
|
|
380
|
+
const candidates = [...await walk(root, ".md"), ...await walk(root, ".html")].filter(
|
|
381
|
+
(f) => !isExcluded(f, exclude)
|
|
382
|
+
);
|
|
383
|
+
for (const file2 of candidates) {
|
|
384
|
+
let raw;
|
|
385
|
+
let stat;
|
|
386
|
+
try {
|
|
387
|
+
stat = await fs4.stat(file2);
|
|
388
|
+
raw = await fs4.readFile(file2, "utf-8");
|
|
389
|
+
} catch {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const text = (file2.toLowerCase().endsWith(".html") ? htmlToText(raw) : raw).trim();
|
|
393
|
+
if (!text) continue;
|
|
394
|
+
const inferred = inferDate(path5.relative(root, file2)).date;
|
|
395
|
+
const ts = /^\d{4}-\d{2}-\d{2}$/.test(inferred) ? `${inferred}T12:00:00.000Z` : new Date(stat.mtimeMs).toISOString();
|
|
396
|
+
events.push({
|
|
397
|
+
id: sha16({ file: file2, head: text.slice(0, 200) }),
|
|
398
|
+
source,
|
|
399
|
+
type: "note",
|
|
400
|
+
timestamp: ts,
|
|
401
|
+
data: { text: text.slice(0, 8e3), title: path5.basename(file2), file: file2 }
|
|
402
|
+
});
|
|
403
|
+
files.push({ path: file2, events: 1 });
|
|
404
|
+
}
|
|
405
|
+
return { events, files };
|
|
406
|
+
}
|
|
407
|
+
async function findConversationsJsons(root) {
|
|
408
|
+
const jsons = await walk(root, ".json");
|
|
409
|
+
const shards = jsons.filter((f) => /^conversations(-\d+)?\.json$/.test(path5.basename(f))).sort();
|
|
410
|
+
if (shards.length) return shards;
|
|
411
|
+
return jsons[0] ? [jsons[0]] : [];
|
|
412
|
+
}
|
|
413
|
+
async function loadConversations(files) {
|
|
414
|
+
const convos = [];
|
|
415
|
+
for (const file2 of files) {
|
|
416
|
+
try {
|
|
417
|
+
const data = JSON.parse(await fs4.readFile(file2, "utf-8"));
|
|
418
|
+
if (Array.isArray(data)) for (const convo of data) convos.push({ convo, file: file2 });
|
|
419
|
+
} catch {
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return convos;
|
|
423
|
+
}
|
|
424
|
+
async function importChatGPT(source, root) {
|
|
425
|
+
const shardFiles = await findConversationsJsons(root);
|
|
426
|
+
if (!shardFiles.length) return { events: [], files: [] };
|
|
427
|
+
const events = [];
|
|
428
|
+
const perFile = new Map(shardFiles.map((p) => [p, 0]));
|
|
429
|
+
const convos = await loadConversations(shardFiles);
|
|
430
|
+
let conversations = 0;
|
|
431
|
+
for (const { convo: c, file: file2 } of convos) {
|
|
432
|
+
const before = events.length;
|
|
433
|
+
const mapping = c.mapping || {};
|
|
434
|
+
for (const nodeId of Object.keys(mapping)) {
|
|
435
|
+
const m = mapping[nodeId]?.message;
|
|
436
|
+
const role = m?.author?.role;
|
|
437
|
+
if (role !== "user" && role !== "assistant") continue;
|
|
438
|
+
const parts = m?.content?.parts || [];
|
|
439
|
+
const text = parts.filter((p) => typeof p === "string").join("\n").trim();
|
|
440
|
+
if (!text) continue;
|
|
441
|
+
const t = m.create_time || c.create_time;
|
|
442
|
+
const ts = t ? new Date(t * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
443
|
+
events.push({
|
|
444
|
+
id: sha16({ nodeId, head: text.slice(0, 200) }),
|
|
445
|
+
source,
|
|
446
|
+
type: role === "user" ? "ai_prompt" : "ai_response",
|
|
447
|
+
timestamp: ts,
|
|
448
|
+
data: role === "user" ? { prompt_text: text } : { response_text: text }
|
|
449
|
+
});
|
|
450
|
+
perFile.set(file2, (perFile.get(file2) ?? 0) + 1);
|
|
451
|
+
}
|
|
452
|
+
if (events.length > before) conversations++;
|
|
453
|
+
}
|
|
454
|
+
return { events, conversations, files: shardFiles.map((p) => ({ path: p, events: perFile.get(p) ?? 0 })) };
|
|
455
|
+
}
|
|
456
|
+
async function importClaudeExport(source, root) {
|
|
457
|
+
const shardFiles = await findConversationsJsons(root);
|
|
458
|
+
if (!shardFiles.length) return { events: [], files: [] };
|
|
459
|
+
const events = [];
|
|
460
|
+
const perFile = new Map(shardFiles.map((p) => [p, 0]));
|
|
461
|
+
const convos = await loadConversations(shardFiles);
|
|
462
|
+
let conversations = 0;
|
|
463
|
+
for (const { convo: c, file: file2 } of convos) {
|
|
464
|
+
const before = events.length;
|
|
465
|
+
const msgs = c.chat_messages || c.messages || [];
|
|
466
|
+
for (const m of msgs) {
|
|
467
|
+
const sender = m.sender || m.role;
|
|
468
|
+
let text = m.text || "";
|
|
469
|
+
if (!text && Array.isArray(m.content)) {
|
|
470
|
+
text = m.content.map((p) => p?.text || "").join("\n");
|
|
471
|
+
}
|
|
472
|
+
text = (text || "").trim();
|
|
473
|
+
if (!text) continue;
|
|
474
|
+
const rawTs = m.created_at || c.created_at;
|
|
475
|
+
const ts = rawTs ? new Date(rawTs).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
476
|
+
const isHuman = sender === "human" || sender === "user";
|
|
477
|
+
events.push({
|
|
478
|
+
id: sha16({ c: c.uuid || c.name, head: text.slice(0, 200), ts }),
|
|
479
|
+
source,
|
|
480
|
+
type: isHuman ? "ai_prompt" : "ai_response",
|
|
481
|
+
timestamp: ts,
|
|
482
|
+
data: isHuman ? { prompt_text: text } : { response_text: text }
|
|
483
|
+
});
|
|
484
|
+
perFile.set(file2, (perFile.get(file2) ?? 0) + 1);
|
|
485
|
+
}
|
|
486
|
+
if (events.length > before) conversations++;
|
|
487
|
+
}
|
|
488
|
+
return { events, conversations, files: shardFiles.map((p) => ({ path: p, events: perFile.get(p) ?? 0 })) };
|
|
489
|
+
}
|
|
490
|
+
async function importFromPath(source, inputPath, exclude) {
|
|
491
|
+
let p = inputPath.trim();
|
|
492
|
+
if (p.startsWith("~")) p = path5.join(os.homedir(), p.slice(1));
|
|
493
|
+
let stat;
|
|
494
|
+
try {
|
|
495
|
+
stat = await fs4.stat(p);
|
|
496
|
+
} catch {
|
|
497
|
+
throw new Error(`Path not found: ${p}`);
|
|
498
|
+
}
|
|
499
|
+
let workDir = p;
|
|
500
|
+
if (stat.isFile() && p.toLowerCase().endsWith(".zip")) {
|
|
501
|
+
const stem = path5.basename(p, path5.extname(p)).replace(/[^\w.-]+/g, "_").slice(0, 80) || "export";
|
|
502
|
+
const dest = path5.join(DATA_DIR, "extract", source, stem);
|
|
503
|
+
await unzipTo(p, dest);
|
|
504
|
+
workDir = dest;
|
|
505
|
+
} else if (stat.isFile()) {
|
|
506
|
+
workDir = path5.dirname(p);
|
|
507
|
+
}
|
|
508
|
+
const detected = await detectChatExport(workDir);
|
|
509
|
+
const effective = detected ?? source;
|
|
510
|
+
let result;
|
|
511
|
+
switch (effective) {
|
|
512
|
+
case "obsidian":
|
|
513
|
+
case "notion":
|
|
514
|
+
result = await importNotes(effective, workDir, exclude);
|
|
515
|
+
break;
|
|
516
|
+
case "chatgpt":
|
|
517
|
+
result = await importChatGPT(effective, workDir);
|
|
518
|
+
break;
|
|
519
|
+
case "claude":
|
|
520
|
+
result = await importClaudeExport(effective, workDir);
|
|
521
|
+
break;
|
|
522
|
+
default:
|
|
523
|
+
result = { events: [], files: [] };
|
|
524
|
+
}
|
|
525
|
+
const account = await readAccountIdentity(effective, workDir);
|
|
526
|
+
return { ...result, ...account ? { account } : {}, source: effective, root: workDir };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ../../lib/compute.ts
|
|
530
|
+
import { promises as fs5 } from "fs";
|
|
531
|
+
import path7 from "path";
|
|
532
|
+
|
|
533
|
+
// ../../lib/careerTodos.ts
|
|
534
|
+
var CAREER_TODOS = [
|
|
535
|
+
{
|
|
536
|
+
id: "schedule",
|
|
537
|
+
title: "Lock in a daily deep-work schedule",
|
|
538
|
+
detail: "Same start time, two or three protected blocks. Consistency is the cheapest level you can buy.",
|
|
539
|
+
level: 2,
|
|
540
|
+
cta: "Set it",
|
|
541
|
+
resources: [
|
|
542
|
+
{ label: "Deep Work \u2014 the core idea", href: "https://www.calnewport.com/books/deep-work/" },
|
|
543
|
+
{ label: "Time-blocking, simply", href: "https://todoist.com/productivity-methods/time-blocking" }
|
|
544
|
+
],
|
|
545
|
+
onPlatform: "Paste your daily logs in Talk to me \u2014 we'll watch whether the schedule actually holds."
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
id: "profile",
|
|
549
|
+
title: "Fix up your LinkedIn and GitHub",
|
|
550
|
+
detail: "A clean headline, a pinned project, a real bio. People look you up before they reply \u2014 give them something.",
|
|
551
|
+
level: 3,
|
|
552
|
+
cta: "Polish",
|
|
553
|
+
resources: [
|
|
554
|
+
{ label: "Make your GitHub profile shine", href: "https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/about-your-profile" },
|
|
555
|
+
{ label: "A LinkedIn that gets replies", href: "https://www.linkedin.com/help/linkedin/answer/a548429" }
|
|
556
|
+
],
|
|
557
|
+
onPlatform: "Sign in with LinkedIn under your profile \u2014 we'll pull it in and flag what's missing."
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
id: "scroll",
|
|
561
|
+
title: "Read the right corner of Twitter daily",
|
|
562
|
+
detail: "Follow 30 people doing the work you want to do.",
|
|
563
|
+
level: 4,
|
|
564
|
+
cta: "Curate",
|
|
565
|
+
resources: [
|
|
566
|
+
{ label: "A list of people to follow", href: "https://x.com/search?q=AI%20engineer&f=user" }
|
|
567
|
+
],
|
|
568
|
+
onPlatform: "Chat with the assistant in Talk to me to build and prune your follow list."
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
id: "post",
|
|
572
|
+
title: "Start posting usefully on Twitter",
|
|
573
|
+
detail: "Two builds-in-public threads a week. Show the work, not the takes.",
|
|
574
|
+
level: 5,
|
|
575
|
+
cta: "Draft",
|
|
576
|
+
resources: [
|
|
577
|
+
{ label: "Who to follow in tech Twitter", href: "https://x.com/search?q=building%20in%20public&f=user" },
|
|
578
|
+
{ label: "How to use X", href: "https://help.x.com/en/using-x" },
|
|
579
|
+
{ label: "How to post a thread", href: "https://help.x.com/en/using-x/creating-a-thread" }
|
|
580
|
+
],
|
|
581
|
+
onPlatform: "Draft threads from your pasted work \u2014 we'll flag the ones worth posting."
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
id: "finetune",
|
|
585
|
+
title: "Ship a fine-tuning / data project",
|
|
586
|
+
detail: "Small, end-to-end, public. It compounds and it's legible to the people you want to reach.",
|
|
587
|
+
level: 6,
|
|
588
|
+
cta: "Start",
|
|
589
|
+
resources: [
|
|
590
|
+
{ label: "Unsloth \u2014 fast fine-tuning notebooks", href: "https://github.com/unslothai/unsloth" },
|
|
591
|
+
{ label: "Hugging Face \u2014 training docs", href: "https://huggingface.co/docs/transformers/en/training" },
|
|
592
|
+
{ label: "Modal \u2014 cheap GPU runs", href: "https://modal.com/docs/guide/gpu" }
|
|
593
|
+
],
|
|
594
|
+
onPlatform: "Paste your build logs in Talk to me \u2014 we'll track the project and surface it in your report."
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
id: "cowork",
|
|
598
|
+
title: "Find a co-working space or build a crew",
|
|
599
|
+
detail: "Isolation caps your ceiling. Get around people who push \u2014 a space, a Discord, a weekly build night.",
|
|
600
|
+
level: 7,
|
|
601
|
+
cta: "Find one",
|
|
602
|
+
resources: [
|
|
603
|
+
{ label: "Hacker houses & spaces", href: "https://www.workfrom.co" },
|
|
604
|
+
{ label: "Find a local builder meetup", href: "https://lu.ma/discover" }
|
|
605
|
+
],
|
|
606
|
+
onPlatform: "Tell the assistant your city \u2014 we'll help you shortlist spaces and communities."
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
id: "internship",
|
|
610
|
+
title: "Get an internship at a frontier lab",
|
|
611
|
+
detail: "Neolab is hiring for exactly your shape. Warm intro available \u2014 draft the DM with one click.",
|
|
612
|
+
level: 9,
|
|
613
|
+
cta: "Draft DM",
|
|
614
|
+
resources: [
|
|
615
|
+
{ label: "Write a cold email that lands", href: "https://www.julian.com/guide/email" },
|
|
616
|
+
{ label: "Work at a Startup", href: "https://www.workatastartup.com" },
|
|
617
|
+
{ label: "Find someone who can refer you", href: "https://www.linkedin.com/search/results/people/" }
|
|
618
|
+
],
|
|
619
|
+
onPlatform: "Draft the DM here, then accept the warm intro in Gifts."
|
|
620
|
+
}
|
|
621
|
+
];
|
|
622
|
+
|
|
623
|
+
// ../../lib/gifts.ts
|
|
624
|
+
var GIFTS = [
|
|
625
|
+
{
|
|
626
|
+
id: "messages",
|
|
627
|
+
title: "Messages",
|
|
628
|
+
kind: "inbox",
|
|
629
|
+
detail: "A Stanford philosophy student \u2014 exceptionally well-read \u2014 wants to compare notes on charting a better path through education.",
|
|
630
|
+
status: "new"
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
id: "cognition",
|
|
634
|
+
title: "Cognition",
|
|
635
|
+
kind: "intro",
|
|
636
|
+
detail: "A warm referral into the team building Cognition's agent platform. Mission-aligned, in SF, exactly your shape.",
|
|
637
|
+
status: "new"
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
id: "vc",
|
|
641
|
+
title: "VC intro",
|
|
642
|
+
kind: "intro",
|
|
643
|
+
detail: "A partner at Lightspeed wants to be connected \u2014 they back deeply technical founders early.",
|
|
644
|
+
status: "viewed"
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
id: "openai",
|
|
648
|
+
title: "OpenAI",
|
|
649
|
+
kind: "opportunity",
|
|
650
|
+
detail: "An opening on OpenAI's post-training team that fits your technical affinity \u2014 they asked to see more.",
|
|
651
|
+
status: "viewed"
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
id: "anthropic",
|
|
655
|
+
title: "Anthropic",
|
|
656
|
+
kind: "opportunity",
|
|
657
|
+
detail: "Your curiosity reads at Anthropic's bar \u2014 a referral toward their interpretability team is unlocked.",
|
|
658
|
+
status: "viewed"
|
|
659
|
+
}
|
|
660
|
+
];
|
|
661
|
+
|
|
662
|
+
// ../../lib/calibration/index.ts
|
|
663
|
+
var DEFAULT_CALIBRATION = {
|
|
664
|
+
version: "2026-07-12.2",
|
|
665
|
+
// = RUBRIC_FINGERPRINT of the shipped npm rubric (packages/coding-analyzer/
|
|
666
|
+
// src/grade/criteria.ts). A unit test fails loud when the rubric changes
|
|
667
|
+
// without this being updated (and re-pushed to the server).
|
|
668
|
+
rubricVersion: "f51b3a93a3",
|
|
669
|
+
// Canonical scale = the one the product owner set for the public report
|
|
670
|
+
// (11=0.01% … 8=2%), extended downward from the old report ladder. The old
|
|
671
|
+
// CodeAnalysisView / npm-viewer maps (7→10%/15%, 6→25%/30%) were drift, not
|
|
672
|
+
// intent.
|
|
673
|
+
scoreBands: [
|
|
674
|
+
{ score: 11, label: "Top 0.01%" },
|
|
675
|
+
{ score: 10, label: "Top 0.1%" },
|
|
676
|
+
{ score: 9, label: "Top 0.5%" },
|
|
677
|
+
{ score: 8, label: "Top 2%" },
|
|
678
|
+
{ score: 7, label: "Top 5%" },
|
|
679
|
+
{ score: 6, label: "Top 15%" },
|
|
680
|
+
{ score: 5, label: "Top 50%" },
|
|
681
|
+
{ score: 4, label: "Top 65%" },
|
|
682
|
+
{ score: 3, label: "Top 80%" },
|
|
683
|
+
{ score: 2, label: "Top 90%" },
|
|
684
|
+
{ score: 1, label: "Top 97%" }
|
|
685
|
+
],
|
|
686
|
+
minRankedScore: 6,
|
|
687
|
+
benchmarkBands: [
|
|
688
|
+
{
|
|
689
|
+
level: "11",
|
|
690
|
+
name: "Superhuman",
|
|
691
|
+
rank: "Top 0.01%",
|
|
692
|
+
oneIn: "1 in 10,000+",
|
|
693
|
+
meaning: "Beyond the normal human ceiling for the trait \u2014 the genuine power-law outlier. Almost no one earns this; when the evidence shows it, it is scored, not rounded down.",
|
|
694
|
+
foundIn: [
|
|
695
|
+
"Fields Medal and Nobel-track researchers",
|
|
696
|
+
"Founders of generational companies",
|
|
697
|
+
"The handful of people a frontier field is named after"
|
|
698
|
+
],
|
|
699
|
+
maxTopPct: 0.01
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
level: "10",
|
|
703
|
+
name: "World-class",
|
|
704
|
+
rank: "Top 0.1%",
|
|
705
|
+
oneIn: "1 in 1,000",
|
|
706
|
+
meaning: "Among the best alive at this trait \u2014 the level entire institutions are built to find.",
|
|
707
|
+
foundIn: [
|
|
708
|
+
"Researchers at frontier AI labs",
|
|
709
|
+
"Faculty at top-5 research universities",
|
|
710
|
+
"IMO / IOI medalists",
|
|
711
|
+
"Founders backed by the top decile of venture firms"
|
|
712
|
+
],
|
|
713
|
+
maxTopPct: 0.1
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
level: "9",
|
|
717
|
+
name: "Exceptional",
|
|
718
|
+
rank: "Top 1%",
|
|
719
|
+
oneIn: "1 in 100",
|
|
720
|
+
meaning: "Clearly exceptional \u2014 the strongest person on most strong teams.",
|
|
721
|
+
foundIn: [
|
|
722
|
+
"PhD students at top programs",
|
|
723
|
+
"Early engineers at breakout startups",
|
|
724
|
+
"YC-class founders",
|
|
725
|
+
"National olympiad finalists"
|
|
726
|
+
],
|
|
727
|
+
maxTopPct: 1
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
level: "8",
|
|
731
|
+
name: "Strong",
|
|
732
|
+
rank: "Top 10%",
|
|
733
|
+
oneIn: "1 in 10",
|
|
734
|
+
meaning: "Strong against the whole population \u2014 the best person in most rooms, not every room.",
|
|
735
|
+
foundIn: [
|
|
736
|
+
"Senior engineers at selective tech companies",
|
|
737
|
+
"Graduates of demanding technical programs",
|
|
738
|
+
"Operators who get promoted everywhere they go"
|
|
739
|
+
],
|
|
740
|
+
maxTopPct: 10
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
level: "5",
|
|
744
|
+
name: "Median",
|
|
745
|
+
rank: "50th percentile",
|
|
746
|
+
oneIn: "1 in 2",
|
|
747
|
+
meaning: 'The middle of the general population. The reference class is all ~8 billion humans \u2014 so even "strong" above already means top 10% of everyone.',
|
|
748
|
+
foundIn: ["The general population \u2014 most people, most places"],
|
|
749
|
+
maxTopPct: 100
|
|
750
|
+
}
|
|
751
|
+
],
|
|
752
|
+
codingTiers: {
|
|
753
|
+
push: { median: 300, sigma: 1.121, tag: "estimated \u2014 log-normal fit", source: "Anthropic 2026 Claude Code study (400k sessions): median engaged user ~8 prompts/day at ~35 words \u2014 ~300 directed words/day; tail shape from its actions-per-prompt distribution. AVERAGE over substantial days, cumulative not spiky." },
|
|
754
|
+
focus: { median: 0.08, sigma: 0.559, tag: "estimated \u2014 proxy-anchored", source: "median: Anthropic 20h/week engaged-user figure implies ~1h/day of true rapid exchange against an 8h working day; ceiling anchored to the ~4h/day deep-work limit (45% share = 1-in-1,000). AVERAGE share, cumulative not spiky." },
|
|
755
|
+
orchestration: { tag: "estimated \u2014 behavior bands", source: "no published concurrency distribution exists; ceiling anchored to the Claude Code lead's documented 10-15 parallel sessions" }
|
|
756
|
+
},
|
|
757
|
+
codingBenchmarks: {
|
|
758
|
+
flow: {
|
|
759
|
+
// typical knowledge worker ≈ 35% of an 8h day focused; ~4h/day is the
|
|
760
|
+
// recognized deep-work ceiling (Newport) ≈ 50% — most sit far below it.
|
|
761
|
+
typicalPctOfDay: 0.35,
|
|
762
|
+
topPctOfDay: 0.5,
|
|
763
|
+
tag: "measured",
|
|
764
|
+
source: "RescueTime 2019 (~2h48m focused/day) \xB7 Cal Newport, Deep Work (~4h/day deep-work ceiling)",
|
|
765
|
+
line: "the best spend ~half the workday in true deep flow; ~4h/day is the human ceiling"
|
|
766
|
+
},
|
|
767
|
+
longestRun: {
|
|
768
|
+
typicalHours: 0.67,
|
|
769
|
+
// ~40 min before the average worker is interrupted
|
|
770
|
+
topHours: 4,
|
|
771
|
+
tag: "measured",
|
|
772
|
+
source: "RescueTime (avg max ~40 min focus before interruption) \xB7 deep-work single-block ceiling ~3\u20134h",
|
|
773
|
+
line: "a top performer can hold a single ~3\u20134h uninterrupted block; the average worker breaks at ~40 min"
|
|
774
|
+
},
|
|
775
|
+
parallelism: {
|
|
776
|
+
topMaxConcurrent: 12,
|
|
777
|
+
// Boris Cherny ~10–15 interactive sessions
|
|
778
|
+
topAvgConcurrent: 6,
|
|
779
|
+
// time-weighted average while active (anecdotal, same source)
|
|
780
|
+
worktreesTip: "3\u20135 git worktrees at once",
|
|
781
|
+
tag: "anecdotal",
|
|
782
|
+
source: "Boris Cherny (Claude Code lead, Anthropic): ~10\u201315 concurrent sessions, 'dozens of Claudes running at all times'; 3\u20135 worktrees = 'the single biggest productivity unlock'",
|
|
783
|
+
line: "the Claude Code lead runs 10\u201315 sessions at once; his #1 tip is 3\u20135 git worktrees in parallel"
|
|
784
|
+
},
|
|
785
|
+
throughput: {
|
|
786
|
+
// Top-0.1% words/day reference lines, anchored to the throughput ladder
|
|
787
|
+
// (THROUGHPUT_LADDER in throughput.ts): score 10 "world-class sustained" =
|
|
788
|
+
// 9k/day, score 11 "superhuman ceiling" = 12k/day. Calibrated to the person's
|
|
789
|
+
// own anchors, not a wild guess — but still a target, not a measured population.
|
|
790
|
+
topAvgWordsPerDay: 9e3,
|
|
791
|
+
topPeakWordsPerDay: 12e3,
|
|
792
|
+
benchLabel: "top 0.1%",
|
|
793
|
+
// No published "words typed/day" for heavy users — the real story is OUTPUT.
|
|
794
|
+
loopsHeadline: ">1,000,000 lines of Rust at 99.8% tests passing",
|
|
795
|
+
loopsDetail: "the Bun runtime was ported Zig\u2192Rust largely autonomously \u2014 hundreds of parallel subagents, two AI reviewers per file",
|
|
796
|
+
tag: "measured",
|
|
797
|
+
source: "The Register, May 2026 (Bun Zig\u2192Rust rewrite)",
|
|
798
|
+
line: "autonomous fleets ported >1M lines of Rust at 99.8% tests passing; a while-loop agent delivered a $50k contract for $297 of compute"
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
function bandLabel(score, doc = DEFAULT_CALIBRATION) {
|
|
803
|
+
const s = Math.max(1, Math.min(11, Math.round(score)));
|
|
804
|
+
let best = null;
|
|
805
|
+
for (const b of doc.scoreBands) if (b.score <= s && (!best || b.score > best.score)) best = b;
|
|
806
|
+
return (best ?? doc.scoreBands[0]).label;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// ../../lib/patterns.ts
|
|
810
|
+
var TOP_STRENGTHS = [
|
|
811
|
+
{
|
|
812
|
+
title: "Curiosity",
|
|
813
|
+
level: bandLabel(9),
|
|
814
|
+
detail: "You go to primary sources and stay until it actually clicks \u2014 and the range of what you've chased, from ML internals to how people learn, is unusual."
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
title: "Agency",
|
|
818
|
+
level: bandLabel(9),
|
|
819
|
+
detail: "You've come at the education problem from a dozen angles over the past several months \u2014 each one a high-quality, high-judgment bet, none of it busywork. You don't wait for permission to start."
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
title: "Ownership",
|
|
823
|
+
level: bandLabel(8),
|
|
824
|
+
detail: "You take the unglamorous parts as yours to close. On the education work you've carried the hard bits end to end instead of handing them off."
|
|
825
|
+
}
|
|
826
|
+
];
|
|
827
|
+
var WORK_ON = [
|
|
828
|
+
{
|
|
829
|
+
title: "Converting strategy to action",
|
|
830
|
+
detail: "You're good at deciding the right move \u2014 the co-working space, the LinkedIn cleanup, the next project \u2014 but it stays a plan. Pick one open intention this week, ship the first concrete step within 48 hours, and log it here so the action actually shows up in your patterns."
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
title: "Consistent schedules",
|
|
834
|
+
detail: "Your hours add up, but they're fragmented \u2014 a block at 9, another at noon, another late at night. Set one fixed start time, protect two or three deep-work blocks a day, and hold the same shape for two weeks before you judge it."
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
title: "Openness to other ideas",
|
|
838
|
+
detail: "You lock in fast, sometimes before the counter-argument is on the table. Before committing to a direction, write the strongest version of the opposing case in two lines, and pull in one person who'll genuinely push back."
|
|
839
|
+
}
|
|
840
|
+
];
|
|
841
|
+
var SECTION_AXES = [
|
|
842
|
+
{ match: /intellig|curios|mind/i, axes: ["Raw ability", "Curiosity"] },
|
|
843
|
+
{ match: /ambitio/i, axes: ["Ambition"] },
|
|
844
|
+
{ match: /people|empath|social/i, axes: ["Empathy"] },
|
|
845
|
+
{ match: /strateg|judg|decision/i, axes: ["Strategy"] },
|
|
846
|
+
{ match: /communicat|conflict|writing/i, axes: ["Communication"] },
|
|
847
|
+
{ match: /conscient|work ethic|disciplin|consisten/i, axes: ["Work ethic", "Consistency"] }
|
|
848
|
+
];
|
|
849
|
+
function rankFromScore(score) {
|
|
850
|
+
return bandLabel(score);
|
|
851
|
+
}
|
|
852
|
+
function derivePatterns(report) {
|
|
853
|
+
const scoreByLabel = new Map(report.capabilities.map((c) => [c.label, c.score]));
|
|
854
|
+
const avgScore = report.capabilities.length ? Math.round(
|
|
855
|
+
report.capabilities.reduce((s, c) => s + c.score, 0) / report.capabilities.length
|
|
856
|
+
) : 6;
|
|
857
|
+
const sectionScore = (title) => {
|
|
858
|
+
const entry = SECTION_AXES.find((e) => e.match.test(title));
|
|
859
|
+
const scores = (entry?.axes ?? []).map((a) => scoreByLabel.get(a)).filter((n) => typeof n === "number");
|
|
860
|
+
return scores.length ? Math.max(...scores) : avgScore;
|
|
861
|
+
};
|
|
862
|
+
const strengths = [];
|
|
863
|
+
const workOn = [];
|
|
864
|
+
for (const sec of report.sections) {
|
|
865
|
+
if (sec.tone === "concern") {
|
|
866
|
+
for (const p of sec.points) workOn.push({ title: sec.title, detail: p.text });
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
const pros = sec.points.filter((p) => !p.candid);
|
|
870
|
+
const cons = sec.points.filter((p) => p.candid);
|
|
871
|
+
if (pros.length) {
|
|
872
|
+
const score = sectionScore(sec.title);
|
|
873
|
+
strengths.push({
|
|
874
|
+
title: sec.title,
|
|
875
|
+
level: rankFromScore(score),
|
|
876
|
+
detail: pros[0].text,
|
|
877
|
+
_score: score
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
for (const c of cons) workOn.push({ title: sec.title, detail: c.text });
|
|
881
|
+
}
|
|
882
|
+
const topStrengths = strengths.sort((a, b) => b._score - a._score).slice(0, 3).map(({ _score, ...card }) => card);
|
|
883
|
+
return { topStrengths, workOn: workOn.slice(0, 4) };
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// ../../lib/evaluate.ts
|
|
887
|
+
var DIMENSIONS = [
|
|
888
|
+
{ key: "ownership", label: "Ownership", offset: 18 },
|
|
889
|
+
{ key: "judgement", label: "Judgement", offset: 12 },
|
|
890
|
+
{ key: "feedback", label: "Openness to feedback", offset: 6 },
|
|
891
|
+
{ key: "growth", label: "Growth curve", offset: 10 },
|
|
892
|
+
{ key: "interests", label: "What they're drawn to", offset: 14 },
|
|
893
|
+
{ key: "drive", label: "How hard they push", offset: 16 },
|
|
894
|
+
{ key: "ambition", label: "Ambition", offset: 15 },
|
|
895
|
+
{ key: "life", label: "What they want from life", offset: 4 },
|
|
896
|
+
{ key: "technical", label: "Technical affinity", offset: 20 },
|
|
897
|
+
{ key: "metacognition", label: "Metacognition", offset: 8 },
|
|
898
|
+
{ key: "morals", label: "Morals & values", offset: 7 }
|
|
899
|
+
];
|
|
900
|
+
function clamp(n, lo = 0, hi = 100) {
|
|
901
|
+
return Math.max(lo, Math.min(hi, n));
|
|
902
|
+
}
|
|
903
|
+
function levelFor(score) {
|
|
904
|
+
if (score >= 85) return "Top 1% \xB7 Anthropic level";
|
|
905
|
+
if (score >= 70) return "Strong \xB7 author level";
|
|
906
|
+
if (score >= 50) return "Developing";
|
|
907
|
+
return "Early signal";
|
|
908
|
+
}
|
|
909
|
+
function dayKey(iso) {
|
|
910
|
+
return iso.slice(0, 10);
|
|
911
|
+
}
|
|
912
|
+
function evaluatePlaceholder(input) {
|
|
913
|
+
const { profile, entries, events, connectedCount } = input;
|
|
914
|
+
const prompts = events.filter((e) => e.type === "ai_prompt").length;
|
|
915
|
+
const responses = events.filter((e) => e.type === "ai_response").length;
|
|
916
|
+
const sessions = new Set(events.map((e) => e.sessionId).filter(Boolean)).size;
|
|
917
|
+
const pastedChars = entries.reduce((sum, e) => sum + e.text.length, 0);
|
|
918
|
+
const hourBuckets = /* @__PURE__ */ new Map();
|
|
919
|
+
const stamp = (iso) => {
|
|
920
|
+
const d = dayKey(iso);
|
|
921
|
+
const hour = new Date(iso).getHours();
|
|
922
|
+
if (!hourBuckets.has(d)) hourBuckets.set(d, /* @__PURE__ */ new Set());
|
|
923
|
+
hourBuckets.get(d).add(hour);
|
|
924
|
+
};
|
|
925
|
+
for (const e of events) {
|
|
926
|
+
if (e.timestamp) stamp(e.timestamp);
|
|
927
|
+
}
|
|
928
|
+
for (const e of entries) stamp(e.createdAt);
|
|
929
|
+
const activeDays = hourBuckets.size;
|
|
930
|
+
const intensity = [];
|
|
931
|
+
const today = /* @__PURE__ */ new Date();
|
|
932
|
+
for (let i = 13; i >= 0; i--) {
|
|
933
|
+
const d = new Date(today.getTime() - i * 864e5);
|
|
934
|
+
const key = d.toISOString().slice(0, 10);
|
|
935
|
+
const hours = hourBuckets.has(key) ? Math.min(16, hourBuckets.get(key).size) : 0;
|
|
936
|
+
intensity.push({ day: key, hours });
|
|
937
|
+
}
|
|
938
|
+
const totalEvents = events.length;
|
|
939
|
+
const signal = Math.min(
|
|
940
|
+
16,
|
|
941
|
+
Math.round(Math.log2(totalEvents + 1) * 1.4 + Math.log2(pastedChars + 1) * 0.6)
|
|
942
|
+
);
|
|
943
|
+
const dimensions = DIMENSIONS.map((d) => {
|
|
944
|
+
const seed = Array.from(d.key).reduce((a, c) => a + c.charCodeAt(0), 0);
|
|
945
|
+
const spread = 46 + (seed * 7 + d.offset) % 42;
|
|
946
|
+
const score = clamp(Math.min(96, spread + signal));
|
|
947
|
+
return {
|
|
948
|
+
key: d.key,
|
|
949
|
+
label: d.label,
|
|
950
|
+
score,
|
|
951
|
+
level: levelFor(score),
|
|
952
|
+
evidence: totalEvents > 0 ? `Inferred from ${prompts} prompts across ${sessions || 1} sessions (placeholder).` : "Connect a source to generate signal."
|
|
953
|
+
};
|
|
954
|
+
});
|
|
955
|
+
const topStrengths = TOP_STRENGTHS;
|
|
956
|
+
const workOn = WORK_ON;
|
|
957
|
+
const todos = CAREER_TODOS;
|
|
958
|
+
const gifts = GIFTS;
|
|
959
|
+
const wantToSee = [
|
|
960
|
+
"You sticking to your schedule",
|
|
961
|
+
"You being more honest with yourself",
|
|
962
|
+
"You being more high-agency"
|
|
963
|
+
];
|
|
964
|
+
const profileFill = profile ? [profile.github, profile.website, profile.things, profile.lookingFor].filter(Boolean).length : 0;
|
|
965
|
+
const progress = clamp(
|
|
966
|
+
Math.round(
|
|
967
|
+
connectedCount * 8 + Math.min(35, Math.log2(totalEvents + 1) * 5) + (profile?.onboarded ? 10 : 0) + profileFill * 2 + Math.min(10, pastedChars / 2500)
|
|
968
|
+
)
|
|
969
|
+
);
|
|
970
|
+
const level = Math.min(10, Math.max(1, Math.floor(progress / 10) + 1));
|
|
971
|
+
const summary = totalEvents > 0 ? `Read ${totalEvents.toLocaleString()} signals across ${sessions || 1} sessions and ${activeDays} active day${activeDays === 1 ? "" : "s"}. Curiosity and agency are already top-decile; consistency is the unlock.` : "Nothing connected yet. Paste something in, or connect a source, and the picture fills in.";
|
|
972
|
+
return {
|
|
973
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
974
|
+
progress,
|
|
975
|
+
level,
|
|
976
|
+
dimensions,
|
|
977
|
+
topStrengths,
|
|
978
|
+
workOn,
|
|
979
|
+
intensity,
|
|
980
|
+
todos,
|
|
981
|
+
gifts,
|
|
982
|
+
wantToSee,
|
|
983
|
+
summary,
|
|
984
|
+
stats: {
|
|
985
|
+
totalEvents,
|
|
986
|
+
prompts,
|
|
987
|
+
responses,
|
|
988
|
+
sessions,
|
|
989
|
+
activeDays,
|
|
990
|
+
pastedChars
|
|
991
|
+
},
|
|
992
|
+
isPlaceholder: true
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// ../../lib/agents/shared/cliAdapter.ts
|
|
997
|
+
var CLAUDE_BIN = process.env.CLAUDE_BIN || "claude";
|
|
998
|
+
|
|
999
|
+
// ../../lib/agents/shared/backends.ts
|
|
1000
|
+
import os2 from "os";
|
|
1001
|
+
var HOME = os2.homedir();
|
|
1002
|
+
|
|
1003
|
+
// ../../lib/agents/shared/tools.ts
|
|
1004
|
+
import { execFile as execFile2 } from "child_process";
|
|
1005
|
+
import { promisify as promisify2 } from "util";
|
|
1006
|
+
var exec = promisify2(execFile2);
|
|
1007
|
+
|
|
1008
|
+
// ../../lib/agents/shared/limitGate.ts
|
|
1009
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
1010
|
+
var MIN = 6e4;
|
|
1011
|
+
var HOUR = 60 * MIN;
|
|
1012
|
+
var STALL_BACKOFFS_MS = [6e4, 3 * MIN, 10 * MIN, 20 * MIN];
|
|
1013
|
+
var SLEEP_CHUNK_MS = 5 * MIN;
|
|
1014
|
+
var CANONICAL = [
|
|
1015
|
+
/claude (ai )?usage limit reached/i,
|
|
1016
|
+
/\busage limit reached\b/i,
|
|
1017
|
+
// "session" variant seen live 2026-07-06: "You've hit your session limit ·
|
|
1018
|
+
// resets 4:10am (America/Los_Angeles)" — unrecognized, it failed EVERY
|
|
1019
|
+
// post-wall stage of the overnight rehearsal instead of pausing. Keep the
|
|
1020
|
+
// subject alternatives in sync with what the CLI actually emits.
|
|
1021
|
+
/you'?ve (?:hit|reached) your (?:usage |session |weekly |daily )?limit/i,
|
|
1022
|
+
/\bsession limit\b[^.]{0,40}resets?/i,
|
|
1023
|
+
/reached your usage limit/i,
|
|
1024
|
+
/5[\s-]?hour limit (?:reached|hit)/i,
|
|
1025
|
+
/limit will reset/i
|
|
1026
|
+
];
|
|
1027
|
+
var LIBERAL = [
|
|
1028
|
+
...CANONICAL,
|
|
1029
|
+
/usage limit/i,
|
|
1030
|
+
/\bsession limit\b/i,
|
|
1031
|
+
/\brate[\s-]?limit(?:ed|s|\b)/i,
|
|
1032
|
+
/\btoo many requests\b/i,
|
|
1033
|
+
/\b429\b/,
|
|
1034
|
+
/quota (?:exceeded|reached|exhausted)/i,
|
|
1035
|
+
/resets? (?:at|in)\b/i,
|
|
1036
|
+
/resets? \d{1,2}(?::\d{2})?\s*(?:am|pm)/i
|
|
1037
|
+
// "resets 4:10am" — no "at" (live form)
|
|
1038
|
+
];
|
|
1039
|
+
var ALS = new AsyncLocalStorage();
|
|
1040
|
+
|
|
1041
|
+
// ../../lib/agents/shared/paths.ts
|
|
1042
|
+
import os3 from "os";
|
|
1043
|
+
import path6 from "path";
|
|
1044
|
+
function polymathHome() {
|
|
1045
|
+
return process.env.POLYMATH_HOME || path6.join(os3.homedir(), ".polymath-society");
|
|
1046
|
+
}
|
|
1047
|
+
function agentDir(agent) {
|
|
1048
|
+
return path6.join(polymathHome(), agent);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// ../../lib/compute.ts
|
|
1052
|
+
async function loadReport() {
|
|
1053
|
+
try {
|
|
1054
|
+
const raw = await fs5.readFile(path7.join(agentDir("report"), "latest.json"), "utf-8");
|
|
1055
|
+
return JSON.parse(raw);
|
|
1056
|
+
} catch {
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async function applyReportPatterns(ev) {
|
|
1061
|
+
const report = await loadReport();
|
|
1062
|
+
if (!report) return ev;
|
|
1063
|
+
const { topStrengths, workOn } = derivePatterns(report);
|
|
1064
|
+
if (!topStrengths.length && !workOn.length) return ev;
|
|
1065
|
+
return {
|
|
1066
|
+
...ev,
|
|
1067
|
+
...topStrengths.length ? { topStrengths } : {},
|
|
1068
|
+
...workOn.length ? { workOn } : {},
|
|
1069
|
+
isPlaceholder: ev.isPlaceholder && report.meta.isPlaceholder
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
async function computeEvaluation() {
|
|
1073
|
+
const [profile, entries, events, connections] = await Promise.all([
|
|
1074
|
+
getProfile(),
|
|
1075
|
+
getEntries(),
|
|
1076
|
+
getAllImported(),
|
|
1077
|
+
getConnections()
|
|
1078
|
+
]);
|
|
1079
|
+
const connectedCount = Object.values(connections).filter((c) => c.connected).length;
|
|
1080
|
+
const evaluation = await applyReportPatterns(
|
|
1081
|
+
evaluatePlaceholder({ profile, entries, events, connectedCount })
|
|
1082
|
+
);
|
|
1083
|
+
await saveEvaluation(evaluation);
|
|
1084
|
+
return evaluation;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// ../../lib/onboarding/notify/notify.ts
|
|
1088
|
+
import { promises as fs6 } from "fs";
|
|
1089
|
+
import path8 from "path";
|
|
1090
|
+
|
|
1091
|
+
// ../../lib/sources.ts
|
|
1092
|
+
var SOURCES = [
|
|
1093
|
+
{
|
|
1094
|
+
id: "claude-code",
|
|
1095
|
+
label: "Claude Code",
|
|
1096
|
+
desc: "Local CLI session logs \u2014 ~/.claude/projects",
|
|
1097
|
+
kind: "auto",
|
|
1098
|
+
available: true
|
|
1099
|
+
},
|
|
1100
|
+
{
|
|
1101
|
+
id: "cursor",
|
|
1102
|
+
label: "Cursor",
|
|
1103
|
+
desc: "Workspace AI history + agent transcripts",
|
|
1104
|
+
kind: "auto",
|
|
1105
|
+
available: true
|
|
1106
|
+
},
|
|
1107
|
+
{
|
|
1108
|
+
id: "codex",
|
|
1109
|
+
label: "Codex",
|
|
1110
|
+
desc: "Codex CLI sessions \u2014 ~/.codex",
|
|
1111
|
+
kind: "auto",
|
|
1112
|
+
available: true
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
id: "obsidian",
|
|
1116
|
+
label: "Obsidian",
|
|
1117
|
+
desc: "Pick your vault folder (.md notes)",
|
|
1118
|
+
kind: "import",
|
|
1119
|
+
available: true
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
id: "apple-notes",
|
|
1123
|
+
label: "Apple Notes",
|
|
1124
|
+
desc: "Local notes \u2014 read via the Notes app",
|
|
1125
|
+
kind: "auto",
|
|
1126
|
+
available: true
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
id: "notion",
|
|
1130
|
+
label: "Notion",
|
|
1131
|
+
desc: "Pick your Notion export (.zip or folder)",
|
|
1132
|
+
kind: "import",
|
|
1133
|
+
available: true
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
id: "chatgpt",
|
|
1137
|
+
label: "ChatGPT",
|
|
1138
|
+
desc: "Pick your ChatGPT data export (.zip)",
|
|
1139
|
+
kind: "import",
|
|
1140
|
+
available: true
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
id: "claude",
|
|
1144
|
+
label: "Claude",
|
|
1145
|
+
desc: "Pick your claude.ai data export (.zip)",
|
|
1146
|
+
kind: "import",
|
|
1147
|
+
available: true
|
|
1148
|
+
}
|
|
1149
|
+
];
|
|
1150
|
+
var SOURCE_LABELS = Object.fromEntries(
|
|
1151
|
+
SOURCES.map((s) => [s.id, s.label])
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
// ../../lib/onboarding/notify/notify.ts
|
|
1155
|
+
var QUEUE_FILE = path8.join(ONBOARDING_DIR, "notifications.json");
|
|
1156
|
+
var DEDUPE_MS = 12 * 60 * 60 * 1e3;
|
|
1157
|
+
var PRUNE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1158
|
+
var notifier = null;
|
|
1159
|
+
async function readQueue() {
|
|
1160
|
+
try {
|
|
1161
|
+
return JSON.parse(await fs6.readFile(QUEUE_FILE, "utf-8"));
|
|
1162
|
+
} catch {
|
|
1163
|
+
return [];
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
async function enqueue(n) {
|
|
1167
|
+
await fs6.mkdir(ONBOARDING_DIR, { recursive: true });
|
|
1168
|
+
let q = await readQueue();
|
|
1169
|
+
q = q.filter((x) => !x.delivered || Date.now() - (Date.parse(x.createdAt) || 0) < PRUNE_MS);
|
|
1170
|
+
if (n.dedupeKey) {
|
|
1171
|
+
const i = q.findIndex((x) => x.dedupeKey === n.dedupeKey);
|
|
1172
|
+
if (i >= 0) {
|
|
1173
|
+
const existing = q[i];
|
|
1174
|
+
const age = Date.now() - (Date.parse(existing.createdAt) || 0);
|
|
1175
|
+
if (age < DEDUPE_MS) {
|
|
1176
|
+
q[i] = { ...existing, title: n.title, body: n.body, href: n.href, kind: n.kind, source: n.source, url: n.url };
|
|
1177
|
+
await fs6.writeFile(QUEUE_FILE, JSON.stringify(q, null, 2), "utf-8");
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
q.splice(i, 1);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
q.push(n);
|
|
1184
|
+
await fs6.writeFile(QUEUE_FILE, JSON.stringify(q, null, 2), "utf-8");
|
|
1185
|
+
}
|
|
1186
|
+
async function notifyReady(source, eventCount) {
|
|
1187
|
+
const label = SOURCE_LABELS[source] ?? source;
|
|
1188
|
+
const n = {
|
|
1189
|
+
id: `${source}-${Date.now()}`,
|
|
1190
|
+
title: "I finished reading you.",
|
|
1191
|
+
body: `Your ${label} history is in \u2014 ${eventCount.toLocaleString()} new signals. Come see what I found.`,
|
|
1192
|
+
href: "/report",
|
|
1193
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1194
|
+
delivered: false,
|
|
1195
|
+
dedupeKey: `ready:${source}`
|
|
1196
|
+
};
|
|
1197
|
+
if (notifier) {
|
|
1198
|
+
await notifier.deliver(n);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
await enqueue(n);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// ../../lib/onboarding/jobs/analyze-trigger.ts
|
|
1205
|
+
import { spawn } from "child_process";
|
|
1206
|
+
import { promises as fs7, existsSync } from "fs";
|
|
1207
|
+
import path9 from "path";
|
|
1208
|
+
var ROOT = process.cwd();
|
|
1209
|
+
var LOCK = path9.join(ROOT, ".data", "analyze.lock");
|
|
1210
|
+
var PENDING = path9.join(ROOT, ".data", "engine-pending.json");
|
|
1211
|
+
function defaultCmd() {
|
|
1212
|
+
if (existsSync(path9.join(ROOT, "scripts", "run-engine.mts"))) return "npx tsx scripts/run-engine.mts";
|
|
1213
|
+
return `"${process.execPath}" "${path9.join(ROOT, "engine-dist", "run-engine.js")}"`;
|
|
1214
|
+
}
|
|
1215
|
+
function mode() {
|
|
1216
|
+
const m = (process.env.POLYMATH_ANALYZE_MODE || "immediate").toLowerCase();
|
|
1217
|
+
return m === "nightly" || m === "off" ? m : "immediate";
|
|
1218
|
+
}
|
|
1219
|
+
function debounceMs() {
|
|
1220
|
+
const v = Number(process.env.POLYMATH_ANALYZE_DEBOUNCE_MS);
|
|
1221
|
+
return Number.isFinite(v) && v >= 0 ? v : 5 * 6e4;
|
|
1222
|
+
}
|
|
1223
|
+
var g = globalThis;
|
|
1224
|
+
function handle() {
|
|
1225
|
+
return g.__analyzeTrigger ??= { timer: null, pending: /* @__PURE__ */ new Set() };
|
|
1226
|
+
}
|
|
1227
|
+
async function scheduleIncrementalAnalysis(source) {
|
|
1228
|
+
const m = mode();
|
|
1229
|
+
if (m === "off") {
|
|
1230
|
+
console.log("[analyze-trigger] POLYMATH_ANALYZE_MODE=off \u2014 not auto-running");
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (m === "nightly") {
|
|
1234
|
+
await markPending(source);
|
|
1235
|
+
console.log(`[analyze-trigger] queued ${source} for the nightly engine run`);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
await markPending(source);
|
|
1239
|
+
const h = handle();
|
|
1240
|
+
h.pending.add(source);
|
|
1241
|
+
if (h.timer) clearTimeout(h.timer);
|
|
1242
|
+
const ms = debounceMs();
|
|
1243
|
+
console.log(`[analyze-trigger] ${source} imported \u2014 engine run in ${Math.round(ms / 1e3)}s (debounced)`);
|
|
1244
|
+
h.timer = setTimeout(() => {
|
|
1245
|
+
h.timer = null;
|
|
1246
|
+
h.pending.clear();
|
|
1247
|
+
void runEngineOnce({ noWindow: true });
|
|
1248
|
+
}, ms);
|
|
1249
|
+
h.timer.unref?.();
|
|
1250
|
+
}
|
|
1251
|
+
async function markPending(source) {
|
|
1252
|
+
await fs7.mkdir(path9.dirname(PENDING), { recursive: true });
|
|
1253
|
+
let cur = { sources: [], since: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1254
|
+
try {
|
|
1255
|
+
cur = JSON.parse(await fs7.readFile(PENDING, "utf-8"));
|
|
1256
|
+
} catch {
|
|
1257
|
+
}
|
|
1258
|
+
const sources = Array.from(/* @__PURE__ */ new Set([...cur.sources || [], source]));
|
|
1259
|
+
await fs7.writeFile(PENDING, JSON.stringify({ sources, since: cur.since || (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
1260
|
+
}
|
|
1261
|
+
async function clearPending() {
|
|
1262
|
+
await fs7.rm(PENDING, { force: true }).catch(() => {
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
async function acquireLock() {
|
|
1266
|
+
try {
|
|
1267
|
+
const pid = Number((await fs7.readFile(LOCK, "utf-8")).trim());
|
|
1268
|
+
if (pid && isAlive(pid)) return false;
|
|
1269
|
+
} catch {
|
|
1270
|
+
}
|
|
1271
|
+
await fs7.mkdir(path9.dirname(LOCK), { recursive: true });
|
|
1272
|
+
await fs7.writeFile(LOCK, String(process.pid));
|
|
1273
|
+
return true;
|
|
1274
|
+
}
|
|
1275
|
+
function isAlive(pid) {
|
|
1276
|
+
try {
|
|
1277
|
+
process.kill(pid, 0);
|
|
1278
|
+
return true;
|
|
1279
|
+
} catch {
|
|
1280
|
+
return false;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
async function runEngineOnce(opts = {}) {
|
|
1284
|
+
if (!await acquireLock()) {
|
|
1285
|
+
console.log("[analyze-trigger] an engine run is already active \u2014 skipping");
|
|
1286
|
+
return 0;
|
|
1287
|
+
}
|
|
1288
|
+
const cmd = process.env.POLYMATH_ANALYZE_CMD || defaultCmd();
|
|
1289
|
+
console.log(`[analyze-trigger] engine run START: ${cmd}`);
|
|
1290
|
+
const env = {
|
|
1291
|
+
...process.env,
|
|
1292
|
+
POLYMATH_RESILIENT: "1",
|
|
1293
|
+
// survive the 5h wall (limitGate)
|
|
1294
|
+
...opts.noWindow ? { POLYMATH_RESILIENT_NO_WINDOW: "1" } : {}
|
|
1295
|
+
};
|
|
1296
|
+
try {
|
|
1297
|
+
const code = await new Promise((resolve) => {
|
|
1298
|
+
const child = spawn("bash", ["-lc", cmd], { cwd: ROOT, env, stdio: "inherit" });
|
|
1299
|
+
child.on("error", (e) => {
|
|
1300
|
+
console.error("[analyze-trigger] engine spawn failed", e);
|
|
1301
|
+
resolve(-1);
|
|
1302
|
+
});
|
|
1303
|
+
child.on("exit", (c) => resolve(c ?? -1));
|
|
1304
|
+
});
|
|
1305
|
+
console.log(`[analyze-trigger] engine run DONE (exit ${code})`);
|
|
1306
|
+
if (code === 0) await clearPending();
|
|
1307
|
+
else console.log("[analyze-trigger] keeping engine-pending.json \u2014 a wake/nightly retry will resume the remaining work");
|
|
1308
|
+
return code;
|
|
1309
|
+
} finally {
|
|
1310
|
+
await fs7.rm(LOCK, { force: true }).catch(() => {
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// ../../lib/onboarding/jobs/runner.ts
|
|
1316
|
+
async function runIngest(jobId) {
|
|
1317
|
+
const state = await getState();
|
|
1318
|
+
const job = state.jobs.find((j) => j.id === jobId);
|
|
1319
|
+
if (!job) return null;
|
|
1320
|
+
if (!job.zipPath) {
|
|
1321
|
+
return patchJob(jobId, { stage: "error", error: "no zip/folder path to ingest" });
|
|
1322
|
+
}
|
|
1323
|
+
let events;
|
|
1324
|
+
let effectiveSource = job.source;
|
|
1325
|
+
try {
|
|
1326
|
+
const imp = await importFromPath(job.source, job.zipPath);
|
|
1327
|
+
events = imp.events;
|
|
1328
|
+
effectiveSource = imp.source ?? job.source;
|
|
1329
|
+
if (events.length === 0) {
|
|
1330
|
+
return patchJob(jobId, { stage: "error", error: "export contained no readable content \u2014 nothing was imported" });
|
|
1331
|
+
}
|
|
1332
|
+
await recordImport(effectiveSource, { ...imp, archive: job.zipPath });
|
|
1333
|
+
if (effectiveSource !== job.source) await patchJob(jobId, { source: effectiveSource });
|
|
1334
|
+
} catch (e) {
|
|
1335
|
+
return patchJob(jobId, { stage: "error", error: `import failed: ${e.message}` });
|
|
1336
|
+
}
|
|
1337
|
+
try {
|
|
1338
|
+
await computeEvaluation();
|
|
1339
|
+
} catch {
|
|
1340
|
+
}
|
|
1341
|
+
void scheduleIncrementalAnalysis(effectiveSource).catch(() => {
|
|
1342
|
+
});
|
|
1343
|
+
const ready = await patchJob(jobId, { stage: "ready" });
|
|
1344
|
+
try {
|
|
1345
|
+
await notifyReady(effectiveSource, events.length);
|
|
1346
|
+
} catch {
|
|
1347
|
+
}
|
|
1348
|
+
return ready;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// ../../lib/onboarding/retrieval/manual.ts
|
|
1352
|
+
function expandHome(p) {
|
|
1353
|
+
return p === "~" || p.startsWith("~/") ? path10.join(os4.homedir(), p.slice(1)) : path10.resolve(p);
|
|
1354
|
+
}
|
|
1355
|
+
async function attachLocalExport(source, filePath, opts = {}) {
|
|
1356
|
+
const resolved = expandHome(filePath.trim());
|
|
1357
|
+
const stat = await fs8.stat(resolved).catch(() => null);
|
|
1358
|
+
if (!stat) throw new Error(`nothing found at ${resolved}`);
|
|
1359
|
+
if (stat.isFile() && !/\.zip$/i.test(resolved)) {
|
|
1360
|
+
throw new Error(`expected a .zip file or a folder, got ${path10.basename(resolved)}`);
|
|
1361
|
+
}
|
|
1362
|
+
let zipPath = resolved;
|
|
1363
|
+
if (stat.isFile()) {
|
|
1364
|
+
await fs8.mkdir(EXPORTS_DIR, { recursive: true });
|
|
1365
|
+
zipPath = path10.join(EXPORTS_DIR, `${source}-manual-${Date.now()}.zip`);
|
|
1366
|
+
await fs8.copyFile(resolved, zipPath);
|
|
1367
|
+
}
|
|
1368
|
+
const state = await getState();
|
|
1369
|
+
const open = [...state.jobs].reverse().find((j) => j.source === source && j.stage !== "ready" && j.stage !== "ingesting");
|
|
1370
|
+
const job = open ?? await createJob(source);
|
|
1371
|
+
const staged = await patchJob(job.id, {
|
|
1372
|
+
stage: "ingesting",
|
|
1373
|
+
zipPath,
|
|
1374
|
+
downloadUrl: void 0,
|
|
1375
|
+
error: void 0,
|
|
1376
|
+
needsUserReason: void 0
|
|
1377
|
+
}) ?? job;
|
|
1378
|
+
if (opts.background) {
|
|
1379
|
+
void runIngest(job.id).catch(() => {
|
|
1380
|
+
});
|
|
1381
|
+
return staged;
|
|
1382
|
+
}
|
|
1383
|
+
return await runIngest(job.id) ?? staged;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// ../../lib/onboarding/types.ts
|
|
1387
|
+
var EXPORT_SOURCES = ["chatgpt", "claude", "notion"];
|
|
1388
|
+
|
|
1389
|
+
// ../../scripts/ingest-export.mts
|
|
1390
|
+
var KINDS = [...EXPORT_SOURCES, "obsidian"];
|
|
1391
|
+
var [kind, file] = process.argv.slice(2);
|
|
1392
|
+
(async () => {
|
|
1393
|
+
if (!kind || !file || !KINDS.includes(kind)) {
|
|
1394
|
+
console.error(`usage: npx tsx scripts/ingest-export.mts <${KINDS.join("|")}> <path-to-zip-or-folder>`);
|
|
1395
|
+
process.exit(2);
|
|
1396
|
+
}
|
|
1397
|
+
if (kind === "obsidian") {
|
|
1398
|
+
console.log(`Ingesting Obsidian vault from ${file} \u2026`);
|
|
1399
|
+
const imp = await importFromPath("obsidian", file);
|
|
1400
|
+
if (imp.events.length === 0) {
|
|
1401
|
+
console.error("FAIL: vault contained no readable notes \u2014 nothing was imported");
|
|
1402
|
+
process.exit(1);
|
|
1403
|
+
}
|
|
1404
|
+
await recordImport(imp.source, imp);
|
|
1405
|
+
try {
|
|
1406
|
+
await computeEvaluation();
|
|
1407
|
+
} catch {
|
|
1408
|
+
}
|
|
1409
|
+
console.log(`DONE \u2014 imported ${imp.events.length} events from ${imp.conversations ?? "?"} notes.`);
|
|
1410
|
+
process.exit(0);
|
|
1411
|
+
}
|
|
1412
|
+
console.log(`Ingesting ${kind} export from ${file} \u2026`);
|
|
1413
|
+
const job = await attachLocalExport(kind, file);
|
|
1414
|
+
if (job.stage === "ready") {
|
|
1415
|
+
console.log(`DONE \u2014 job ${job.id}: data imported, evaluation refreshed.`);
|
|
1416
|
+
} else {
|
|
1417
|
+
console.log(`Job ${job.id} ended in stage "${job.stage}"${job.error ? `: ${job.error}` : ""}`);
|
|
1418
|
+
}
|
|
1419
|
+
process.exit(job.stage === "error" ? 1 : 0);
|
|
1420
|
+
})().catch((e) => {
|
|
1421
|
+
console.error("FAIL", e.message);
|
|
1422
|
+
process.exit(1);
|
|
1423
|
+
});
|