polymath-society 0.2.18 → 0.2.20

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/index.js CHANGED
@@ -170,6 +170,97 @@ function isHarnessEntry(e) {
170
170
  return e.isMeta === true || e.isCompactSummary === true;
171
171
  }
172
172
 
173
+ // ../coding-core/dist/codex.js
174
+ var SYSTEM_REMINDER_RE2 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
175
+ var CODEX_INJECTED_TEXT_RE = /^\s*(<(environment_context|user_instructions|ENVIRONMENT_CONTEXT|turn_aborted)\b|#\s*AGENTS\.md instructions\b)/;
176
+ function isCodexInjectedUserText(text2) {
177
+ return CODEX_INJECTED_TEXT_RE.test(text2);
178
+ }
179
+ function codexContentText(content) {
180
+ if (typeof content === "string")
181
+ return content;
182
+ if (!Array.isArray(content))
183
+ return "";
184
+ const parts = [];
185
+ for (const item of content) {
186
+ if (typeof item === "string") {
187
+ parts.push(item);
188
+ continue;
189
+ }
190
+ if (item && typeof item === "object") {
191
+ const it = item;
192
+ if (it.type === "input_text" || it.type === "output_text" || it.type === "text")
193
+ parts.push(it.text || "");
194
+ }
195
+ }
196
+ return parts.join("\n");
197
+ }
198
+ function sniffCodexRaw(raw) {
199
+ let checked = 0;
200
+ for (const lineRaw of raw.split("\n")) {
201
+ const line = lineRaw.trim();
202
+ if (!line)
203
+ continue;
204
+ if (checked++ >= 5)
205
+ break;
206
+ try {
207
+ const e = JSON.parse(line);
208
+ if (e.type === "session_meta" || e.type === "response_item" || e.type === "turn_context")
209
+ return true;
210
+ if (e.type === "user" || e.type === "assistant" || e.type === "summary" || e.type === "queue-operation")
211
+ return false;
212
+ } catch {
213
+ }
214
+ }
215
+ return false;
216
+ }
217
+ var EXIT_CODE_RE = /(?:Process exited with code|Exit code:)\s+(\d+)/;
218
+ function extractCodexEvents(raw) {
219
+ const out = [];
220
+ for (const lineRaw of raw.split("\n")) {
221
+ const line = lineRaw.trim();
222
+ if (!line)
223
+ continue;
224
+ let e;
225
+ try {
226
+ e = JSON.parse(line);
227
+ } catch {
228
+ continue;
229
+ }
230
+ if (e.type !== "response_item")
231
+ continue;
232
+ const p = e.payload ?? {};
233
+ const t = typeof e.timestamp === "string" ? Date.parse(e.timestamp) : NaN;
234
+ const ptype = p.type;
235
+ if (ptype === "message") {
236
+ const role = p.role;
237
+ const text2 = codexContentText(p.content).replace(SYSTEM_REMINDER_RE2, "").trim();
238
+ if (!text2)
239
+ continue;
240
+ if (role === "user") {
241
+ if (isCodexInjectedUserText(text2))
242
+ continue;
243
+ out.push({ kind: "user", t, text: text2 });
244
+ } else if (role === "assistant") {
245
+ out.push({ kind: "assistant", t, text: text2 });
246
+ }
247
+ continue;
248
+ }
249
+ if (ptype === "function_call" || ptype === "custom_tool_call" || ptype === "local_shell_call") {
250
+ const name17 = typeof p.name === "string" && p.name ? p.name : ptype === "local_shell_call" ? "shell" : String(ptype);
251
+ const input = typeof p.arguments === "string" ? p.arguments : typeof p.input === "string" ? p.input : "";
252
+ out.push({ kind: "tool", t, name: name17, input });
253
+ continue;
254
+ }
255
+ if (ptype === "function_call_output" || ptype === "custom_tool_call_output") {
256
+ const output = typeof p.output === "string" ? p.output : "";
257
+ const m = EXIT_CODE_RE.exec(output.slice(0, 200));
258
+ out.push({ kind: "tool_output", t, output, isError: !!m && m[1] !== "0" });
259
+ }
260
+ }
261
+ return out;
262
+ }
263
+
173
264
  // ../coding-core/dist/raw.js
174
265
  var SOURCE_ROOT_ENV_VARS = [
175
266
  "CLAUDE_PROJECTS_DIR",
@@ -420,7 +511,6 @@ function codexText(content) {
420
511
  }
421
512
  return "";
422
513
  }
423
- var CODEX_INJECTED_RE = /^\s*<(environment_context|user_instructions)>/;
424
514
  function parseCodex(raw, file2) {
425
515
  let sessionId = path.basename(file2, ".jsonl");
426
516
  const s = blank(sessionId, "codex", file2);
@@ -460,7 +550,7 @@ function parseCodex(raw, file2) {
460
550
  const role = p.role;
461
551
  const text2 = codexText(p.content).replace(SYSTEM_REMINDER_RE, "").trim();
462
552
  if (role === "user") {
463
- if (text2 && !CODEX_INJECTED_RE.test(text2)) {
553
+ if (text2 && !isCodexInjectedUserText(text2)) {
464
554
  s.humanTurns++;
465
555
  s.humanChars += text2.length;
466
556
  if (!s.firstHuman)
@@ -1135,6 +1225,22 @@ function extractText2(content) {
1135
1225
  function isToolResultOnly(content) {
1136
1226
  return Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
1137
1227
  }
1228
+ function extractUserMessagesCodex(raw) {
1229
+ const out = [];
1230
+ let lastTs = null;
1231
+ for (const ev of extractCodexEvents(raw)) {
1232
+ const t = ev.t;
1233
+ const gapMs = Number.isFinite(t) && lastTs != null ? t - lastTs : NaN;
1234
+ if (Number.isFinite(t))
1235
+ lastTs = t;
1236
+ if (ev.kind !== "user")
1237
+ continue;
1238
+ const text2 = humanTypedText(ev.text);
1239
+ if (text2 && Number.isFinite(t))
1240
+ out.push({ t, text: text2, gapMs });
1241
+ }
1242
+ return out.sort((a, b) => a.t - b.t);
1243
+ }
1138
1244
  async function extractUserMessages(file2) {
1139
1245
  let raw;
1140
1246
  try {
@@ -1142,6 +1248,8 @@ async function extractUserMessages(file2) {
1142
1248
  } catch {
1143
1249
  return [];
1144
1250
  }
1251
+ if (sniffCodexRaw(raw))
1252
+ return extractUserMessagesCodex(raw);
1145
1253
  const out = [];
1146
1254
  const userTexts = /* @__PURE__ */ new Set();
1147
1255
  const seenUuids = /* @__PURE__ */ new Set();
@@ -1196,6 +1304,49 @@ function createMessageDeduper() {
1196
1304
  return true;
1197
1305
  };
1198
1306
  }
1307
+ function extractMessagesCodex(raw) {
1308
+ const out = [];
1309
+ let aiProse = [];
1310
+ let aiTools = {};
1311
+ let aiStart = 0;
1312
+ const flushAI = () => {
1313
+ if (!aiProse.length && Object.keys(aiTools).length === 0)
1314
+ return;
1315
+ const toolStr = Object.entries(aiTools).map(([n, c]) => c > 1 ? `${n}\xD7${c}` : n).join(", ");
1316
+ let prose = aiProse.join(" ").replace(/\s+/g, " ").trim();
1317
+ if (prose.length > 2200)
1318
+ prose = prose.slice(0, 2200) + "\u2026";
1319
+ let text2 = prose;
1320
+ if (toolStr)
1321
+ text2 = (text2 ? text2 + " " : "") + `[ran ${toolStr}]`;
1322
+ if (text2)
1323
+ out.push({ t: aiStart, role: "ai", text: text2 });
1324
+ aiProse = [];
1325
+ aiTools = {};
1326
+ };
1327
+ for (const ev of extractCodexEvents(raw)) {
1328
+ if (!Number.isFinite(ev.t))
1329
+ continue;
1330
+ if (ev.kind === "user") {
1331
+ const text2 = humanTypedText(ev.text);
1332
+ if (text2) {
1333
+ flushAI();
1334
+ out.push({ t: ev.t, role: "user", text: text2 });
1335
+ }
1336
+ } else if (ev.kind === "assistant") {
1337
+ if (!aiProse.length && Object.keys(aiTools).length === 0)
1338
+ aiStart = ev.t;
1339
+ if (ev.text)
1340
+ aiProse.push(ev.text);
1341
+ } else if (ev.kind === "tool") {
1342
+ if (!aiProse.length && Object.keys(aiTools).length === 0)
1343
+ aiStart = ev.t;
1344
+ aiTools[ev.name] = (aiTools[ev.name] ?? 0) + 1;
1345
+ }
1346
+ }
1347
+ flushAI();
1348
+ return out.sort((a, b) => a.t - b.t);
1349
+ }
1199
1350
  async function extractMessages(file2) {
1200
1351
  let raw;
1201
1352
  try {
@@ -1203,6 +1354,8 @@ async function extractMessages(file2) {
1203
1354
  } catch {
1204
1355
  return [];
1205
1356
  }
1357
+ if (sniffCodexRaw(raw))
1358
+ return extractMessagesCodex(raw);
1206
1359
  const out = [];
1207
1360
  const userTexts = /* @__PURE__ */ new Set();
1208
1361
  const seenUuids = /* @__PURE__ */ new Set();
@@ -2348,7 +2501,7 @@ var LABEL_BY_UIKEY = Object.fromEntries(GRADED_CRITERIA2.map((c) => [c.uiKey, c.
2348
2501
 
2349
2502
  // src/grade/materialize.ts
2350
2503
  import { promises as fs3 } from "fs";
2351
- var SYSTEM_REMINDER_RE2 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
2504
+ var SYSTEM_REMINDER_RE3 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
2352
2505
  var INJECT_GIST = 120;
2353
2506
  var INJECTED = [
2354
2507
  { re: /<task-notification>[\s\S]*?<\/task-notification>/g, label: "bg task", gistRe: /<summary>([\s\S]*?)<\/summary>/ },
@@ -2368,7 +2521,7 @@ function collapseInjected(input) {
2368
2521
  return "";
2369
2522
  });
2370
2523
  }
2371
- return { text: text2.replace(SYSTEM_REMINDER_RE2, "").trim(), markers };
2524
+ return { text: text2.replace(SYSTEM_REMINDER_RE3, "").trim(), markers };
2372
2525
  }
2373
2526
  function extractText3(content) {
2374
2527
  if (typeof content === "string") return content;
@@ -19982,7 +20135,7 @@ import path16 from "node:path";
19982
20135
 
19983
20136
  // ../../lib/agents/coding/materialize.ts
19984
20137
  import { promises as fs11 } from "fs";
19985
- var SYSTEM_REMINDER_RE3 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
20138
+ var SYSTEM_REMINDER_RE4 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
19986
20139
  var INJECT_GIST2 = 120;
19987
20140
  var INJECTED2 = [
19988
20141
  { re: /<task-notification>[\s\S]*?<\/task-notification>/g, label: "bg task", gistRe: /<summary>([\s\S]*?)<\/summary>/ },
@@ -20002,7 +20155,7 @@ function collapseInjected2(input) {
20002
20155
  return "";
20003
20156
  });
20004
20157
  }
20005
- return { text: text2.replace(SYSTEM_REMINDER_RE3, "").trim(), markers };
20158
+ return { text: text2.replace(SYSTEM_REMINDER_RE4, "").trim(), markers };
20006
20159
  }
20007
20160
  function extractText4(content) {
20008
20161
  if (typeof content === "string") return content;
@@ -20019,6 +20172,38 @@ function isToolResultOnly3(content) {
20019
20172
  return Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
20020
20173
  }
20021
20174
  var AI_GIST2 = 160;
20175
+ function materializeCodex(raw) {
20176
+ const lines = [];
20177
+ let humanTurns = 0, humanChars = 0;
20178
+ let aiLastProse = "";
20179
+ const aiTools = /* @__PURE__ */ new Map();
20180
+ const flushAI = () => {
20181
+ if (!aiLastProse && aiTools.size === 0) return;
20182
+ const tools = [...aiTools.entries()].map(([n, c]) => c > 1 ? `${n}\xD7${c}` : n).join(", ");
20183
+ const gist = aiLastProse ? aiLastProse.length > AI_GIST2 ? aiLastProse.slice(0, AI_GIST2) + "\u2026" : aiLastProse : "";
20184
+ const bits = [];
20185
+ if (gist) bits.push(gist);
20186
+ if (tools) bits.push(`ran ${tools}`);
20187
+ lines.push(`[AI: ${bits.join(" \xB7 ")}]`);
20188
+ aiLastProse = "";
20189
+ aiTools.clear();
20190
+ };
20191
+ for (const ev of extractCodexEvents(raw)) {
20192
+ if (ev.kind === "user") {
20193
+ flushAI();
20194
+ humanTurns++;
20195
+ humanChars += ev.text.length;
20196
+ lines.push(`>> ${ev.text}`);
20197
+ } else if (ev.kind === "assistant") {
20198
+ const prose = ev.text.replace(/\s+/g, " ").trim();
20199
+ if (prose) aiLastProse = prose;
20200
+ } else if (ev.kind === "tool") {
20201
+ aiTools.set(ev.name, (aiTools.get(ev.name) ?? 0) + 1);
20202
+ }
20203
+ }
20204
+ flushAI();
20205
+ return { text: lines.join("\n"), humanTurns, humanChars };
20206
+ }
20022
20207
  async function materializeSession2(file2) {
20023
20208
  let raw;
20024
20209
  try {
@@ -20026,6 +20211,7 @@ async function materializeSession2(file2) {
20026
20211
  } catch {
20027
20212
  return null;
20028
20213
  }
20214
+ if (sniffCodexRaw(raw)) return materializeCodex(raw);
20029
20215
  const lines = [];
20030
20216
  let humanTurns = 0, humanChars = 0;
20031
20217
  const pendingQueued = [];
@@ -20558,124 +20744,6 @@ function handleDiagnostic(method, body, key2) {
20558
20744
  return { status: 200, json: { status: job.status, startedAt: job.startedAt, ...job.status === "done" ? { diagnostic: job.result } : {}, ...job.status === "failed" ? { error: job.error } : {} } };
20559
20745
  }
20560
20746
 
20561
- // ../../lib/agents/coding/chat-context.ts
20562
- var RUNGS_BY_KEY = new Map(CODING_CRITERIA.map((c) => [c.key, c.rungs ?? []]));
20563
- var clip = (s, n) => {
20564
- const t = (s || "").replace(/\s+/g, " ").trim();
20565
- return t.length > n ? t.slice(0, n - 1).replace(/\s+\S*$/, "") + "\u2026" : t;
20566
- };
20567
- function criterionBlock(c) {
20568
- if (!c.graded || c.dist.n <= 0) return null;
20569
- const lines = [];
20570
- const mean2 = c.dist.mean != null ? `${c.dist.mean}` : "\u2014";
20571
- lines.push(`### ${c.label} (your mean ${mean2} across ${c.dist.n} graded session${c.dist.n === 1 ? "" : "s"})`);
20572
- lines.push(`What it measures: ${clip(c.definition, 400)}`);
20573
- const rungs = RUNGS_BY_KEY.get(c.key) ?? [];
20574
- if (rungs.length) {
20575
- lines.push(`The ladder I grade against:`);
20576
- for (const r of rungs) lines.push(` L${r.level} \u2014 ${r.marker}`);
20577
- }
20578
- const takes = (c.takes || []).slice(0, 5);
20579
- if (takes.length) {
20580
- lines.push(`How I scored your sessions here:`);
20581
- for (const t of takes) {
20582
- const sc = t.score != null ? `${t.score}` : t.best != null ? `~${t.best}` : "\u2014";
20583
- const parts = [` \u2022 "${clip(t.title, 80)}" (${(t.date || "").slice(0, 10)}) \u2014 scored ${sc}${t.confidence ? ` (${t.confidence})` : ""}.`];
20584
- if (t.instance) parts.push(`What showed it: ${clip(t.instance, 240)}`);
20585
- if (t.why) parts.push(`Why that score: ${clip(t.why, 320)}`);
20586
- const qs = (t.quotes || []).filter(Boolean).slice(0, 2).map((q) => `"${clip(q, 160)}"`);
20587
- if (qs.length) parts.push(`You said: ${qs.join(" / ")}`);
20588
- lines.push(parts.join(" "));
20589
- }
20590
- }
20591
- return lines.join("\n");
20592
- }
20593
- function buildCodingContext(profile) {
20594
- const out = [];
20595
- const t = profile.throughput;
20596
- const p = profile.parallelism;
20597
- const meta = [];
20598
- if (t?.sessions != null) meta.push(`${t.sessions} interactive coding sessions`);
20599
- if (profile.gradedSessions != null) meta.push(`${profile.gradedSessions} graded`);
20600
- if (p?.maxConcurrency != null) meta.push(`peak ${p.maxConcurrency} sessions in parallel`);
20601
- if (meta.length) out.push(`HOW THEY WORK: ${meta.join(" \xB7 ")}.`);
20602
- const blocks = (profile.criteria || []).map(criterionBlock).filter((b) => !!b);
20603
- if (blocks.length) {
20604
- out.push(`
20605
- YOUR GRADED CRITERIA \u2014 the rubric ladder and how I scored you on each, with your own words as the receipts:`);
20606
- out.push(blocks.join("\n\n"));
20607
- } else {
20608
- out.push(`
20609
- (No criteria have been graded yet, so I can only speak to how you work, not the qualitative scores.)`);
20610
- }
20611
- return out.join("\n");
20612
- }
20613
- var CODING_CHAT_SYSTEM = `You are the analyst who graded this person's coding-agent sessions (their Claude Code / Codex logs) against a FIXED rubric, and you are now talking WITH them about their report. Below you are given that rubric \u2014 the definition and the level ladder for each criterion \u2014 AND your own per-session grades: the exact moment that keyed each score, WHY you placed it on that rung, and their verbatim quotes. Everything you claim comes from that material.
20614
-
20615
- HOW TO ANSWER:
20616
- - Ground every claim in the receipts you were given. When they ask "why did I get that score on X", name the specific session, the rung you landed them on, what that rung requires, and quote their actual words. Never invent a session, a score, a rung, or a criterion that isn't below.
20617
- - The rubric is the authority. If they push on a score, explain the ladder honestly: what rung they hit and what the NEXT rung would concretely have required of them. Don't inflate a score to be nice and don't apologize for a low one \u2014 being read accurately is the point. If they're genuinely strong, say so plainly.
20618
- - Be honest about the instrument's limits. These grades come only from their coding logs, which UNDER-observe real skill (a lot of thinking never reaches the transcript). When it's relevant, say what you can and can't actually see from the logs rather than overclaiming.
20619
- - Voice: warm, plain, declarative, second person. Specific, never a horoscope, never flattery. Numbers are calibration, not a verdict \u2014 don't recite them at the person.
20620
- - Keep replies tight and human unless they ask you to go deep. Output ONLY your reply \u2014 no preamble, no meta.
20621
-
20622
- Do NOT use em dashes. Use a comma or a period instead.`;
20623
- function stripSlop(raw) {
20624
- return (raw || "").trim().replace(/\s*—\s*/g, ", ").replace(/\s+–\s+/g, ", ").replace(/\s+,/g, ",");
20625
- }
20626
- function buildCodingChatPrompt(profile, messages) {
20627
- const context = buildCodingContext(profile);
20628
- const history = messages.map((m) => `${m.role === "user" ? "Them" : "You"}: ${m.content}`).join("\n\n");
20629
- return `YOUR READ OF THIS PERSON (the rubric + your grades + their receipts):
20630
- ${context}
20631
-
20632
- --- the conversation so far ---
20633
- ${history}
20634
-
20635
- Write your next reply to them now.`;
20636
- }
20637
-
20638
- // src/codingChat.ts
20639
- async function handleCodingChat(profile, dataDir, body, res) {
20640
- const raw = Array.isArray(body.messages) ? body.messages : [];
20641
- const messages = raw.filter((m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string").slice(-20);
20642
- if (!messages.length) {
20643
- res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
20644
- res.end(JSON.stringify({ error: "no messages" }));
20645
- return;
20646
- }
20647
- const prompt = buildCodingChatPrompt(profile, messages);
20648
- res.writeHead(200, {
20649
- "Content-Type": "application/x-ndjson; charset=utf-8",
20650
- "Cache-Control": "no-store, no-transform",
20651
- "X-Accel-Buffering": "no"
20652
- });
20653
- const send = (o) => {
20654
- try {
20655
- res.write(JSON.stringify(o) + "\n");
20656
- } catch {
20657
- }
20658
- };
20659
- try {
20660
- const run3 = await invokeAgent({
20661
- prompt,
20662
- systemPrompt: CODING_CHAT_SYSTEM,
20663
- // Everything it needs is in the prompt; dataDir is a harmless read-only
20664
- // sandbox. maxTurns 2 → answer directly, never a tool-loop.
20665
- addDir: dataDir,
20666
- allowedTools: ["Read"],
20667
- maxTurns: 2,
20668
- model: "sonnet",
20669
- onText: (delta) => send({ t: "delta", v: delta })
20670
- });
20671
- send({ t: "done", reply: stripSlop(run3.raw || "") || "(no response)" });
20672
- } catch {
20673
- send({ t: "error", message: "Couldn't reach your local Claude \u2014 is Claude Code (or Codex) installed and logged in?" });
20674
- } finally {
20675
- res.end();
20676
- }
20677
- }
20678
-
20679
20747
  // src/personApi.ts
20680
20748
  import { promises as fs18 } from "fs";
20681
20749
  import path22 from "path";
@@ -21237,6 +21305,137 @@ ${dataMap}`,
21237
21305
  }
21238
21306
  }
21239
21307
 
21308
+ // ../../lib/agents/coding/chat-context.ts
21309
+ var RUNGS_BY_KEY = new Map(CODING_CRITERIA.map((c) => [c.key, c.rungs ?? []]));
21310
+ var clip = (s, n) => {
21311
+ const t = (s || "").replace(/\s+/g, " ").trim();
21312
+ return t.length > n ? t.slice(0, n - 1).replace(/\s+\S*$/, "") + "\u2026" : t;
21313
+ };
21314
+ function criterionBlock(c) {
21315
+ if (!c.graded || c.dist.n <= 0) return null;
21316
+ const lines = [];
21317
+ const mean2 = c.dist.mean != null ? `${c.dist.mean}` : "\u2014";
21318
+ lines.push(`### ${c.label} (your mean ${mean2} across ${c.dist.n} graded session${c.dist.n === 1 ? "" : "s"})`);
21319
+ lines.push(`What it measures: ${clip(c.definition, 400)}`);
21320
+ const rungs = RUNGS_BY_KEY.get(c.key) ?? [];
21321
+ if (rungs.length) {
21322
+ lines.push(`The ladder I grade against:`);
21323
+ for (const r of rungs) lines.push(` L${r.level} \u2014 ${r.marker}`);
21324
+ }
21325
+ const takes = (c.takes || []).slice(0, 5);
21326
+ if (takes.length) {
21327
+ lines.push(`How I scored your sessions here:`);
21328
+ for (const t of takes) {
21329
+ const sc = t.score != null ? `${t.score}` : t.best != null ? `~${t.best}` : "\u2014";
21330
+ const parts = [` \u2022 "${clip(t.title, 80)}" (${(t.date || "").slice(0, 10)}) \u2014 scored ${sc}${t.confidence ? ` (${t.confidence})` : ""}.`];
21331
+ if (t.instance) parts.push(`What showed it: ${clip(t.instance, 240)}`);
21332
+ if (t.why) parts.push(`Why that score: ${clip(t.why, 320)}`);
21333
+ const qs = (t.quotes || []).filter(Boolean).slice(0, 2).map((q) => `"${clip(q, 160)}"`);
21334
+ if (qs.length) parts.push(`You said: ${qs.join(" / ")}`);
21335
+ lines.push(parts.join(" "));
21336
+ }
21337
+ }
21338
+ return lines.join("\n");
21339
+ }
21340
+ var CODING_DATA_HOME_MAP = `THE DATA HOME YOU CAN READ (tools: Read, Grep, Glob; every path is relative to the extra read-only directory you were given):
21341
+ - coding/sessions.json \u2014 every session: title, times, activeMin, humanTurns, tool counts, klass (only "interactive" sessions are the person; the rest are machine runs), duplicateOf (fork copies \u2014 skip them or you double-count).
21342
+ - coding/focus.json \u2014 the flow analysis: per-day half-hour cells, steering runs, gaps, false starts, workstream features, causal findings. coding/flow.json \u2014 the older looser metric; the report calls it "engaged time", never flow. Do not conflate the two.
21343
+ - coding/day-digest.json and coding/day-chart.json \u2014 per-day narratives and per-conversation active intervals.
21344
+ - coding/projects.json \u2014 per-repo attribution. coding/delegation/ \u2014 delegation pattern reads.
21345
+ - coding/grades/*.json \u2014 per-session rubric grades (score on the 1-11 ladder, keyed instance, why, verbatim quotes). coding/walkthroughs/ \u2014 per-session walkthroughs.
21346
+ - coding/aggregate.json (ability ceilings + the bigPicture nutshell), coding/expertise.json, coding/gap.json (ranked technique recommendations), coding/coaching.json, coding/frontier.json, coding/frontier-detail.json, coding/concurrency.json \u2014 each self-describing; read the file before citing it.
21347
+ Trust the filesystem over this list: Glob/ls a directory before assuming its contents, and a file may simply not exist yet on a fresh run \u2014 say so instead of guessing. Budget 2-4 lookups per question, then answer.`;
21348
+ function buildCodingContext(profile) {
21349
+ const out = [];
21350
+ const t = profile.throughput;
21351
+ const p = profile.parallelism;
21352
+ const meta = [];
21353
+ if (t?.sessions != null) meta.push(`${t.sessions} interactive coding sessions`);
21354
+ if (profile.gradedSessions != null) meta.push(`${profile.gradedSessions} graded`);
21355
+ if (p?.maxConcurrency != null) meta.push(`peak ${p.maxConcurrency} sessions in parallel`);
21356
+ if (meta.length) out.push(`HOW THEY WORK: ${meta.join(" \xB7 ")}.`);
21357
+ const blocks = (profile.criteria || []).map(criterionBlock).filter((b) => !!b);
21358
+ if (blocks.length) {
21359
+ out.push(`
21360
+ YOUR GRADED CRITERIA \u2014 the rubric ladder and how I scored you on each, with your own words as the receipts:`);
21361
+ out.push(blocks.join("\n\n"));
21362
+ } else {
21363
+ out.push(`
21364
+ NO QUALITATIVE GRADES YET: the rubric grading pass has not scored any sessions, so there are no criterion scores, rungs, or graded receipts to cite. If they ask about grades or scores, say that plainly and never fabricate one. Everything measurable is still answerable: read the deterministic artifacts in the data home (sessions, focus/flow, projects, day digests) and give them real numbers with dates instead of shrugging.`);
21365
+ }
21366
+ out.push(`
21367
+ ${CODING_DATA_HOME_MAP}`);
21368
+ return out.join("\n");
21369
+ }
21370
+ var CODING_CHAT_SYSTEM = `You are the analyst who graded this person's coding-agent sessions (their Claude Code / Codex logs) against a FIXED rubric, and you are now talking WITH them about their report. Below you are given that rubric \u2014 the definition and the level ladder for each criterion \u2014 AND your own per-session grades: the exact moment that keyed each score, WHY you placed it on that rung, and their verbatim quotes. Everything you claim comes from that material or from the served data-home artifacts you can read with your tools (the grounding block maps them) \u2014 never invent a number, score, session, or quote that no file backs. The person's raw session logs are in bounds only through the artifacts present in the data home; do not hunt outside the directory you were given.
21371
+
21372
+ HOW TO ANSWER:
21373
+ - Ground every claim in the receipts you were given. When they ask "why did I get that score on X", name the specific session, the rung you landed them on, what that rung requires, and quote their actual words. Never invent a session, a score, a rung, or a criterion that isn't below.
21374
+ - The rubric is the authority. If they push on a score, explain the ladder honestly: what rung they hit and what the NEXT rung would concretely have required of them. Don't inflate a score to be nice and don't apologize for a low one \u2014 being read accurately is the point. If they're genuinely strong, say so plainly.
21375
+ - Be honest about the instrument's limits. These grades come only from their coding logs, which UNDER-observe real skill (a lot of thinking never reaches the transcript). When it's relevant, say what you can and can't actually see from the logs rather than overclaiming.
21376
+ - Voice: warm, plain, declarative, second person. Specific, never a horoscope, never flattery. Numbers are calibration, not a verdict \u2014 don't recite them at the person.
21377
+ - Keep replies tight and human unless they ask you to go deep. Output ONLY your reply \u2014 no preamble, no meta.
21378
+
21379
+ Do NOT use em dashes. Use a comma or a period instead.`;
21380
+ function stripSlop(raw) {
21381
+ return (raw || "").trim().replace(/\s*—\s*/g, ", ").replace(/\s+–\s+/g, ", ").replace(/\s+,/g, ",");
21382
+ }
21383
+ function buildCodingChatPrompt(profile, messages) {
21384
+ const context = buildCodingContext(profile);
21385
+ const history = messages.map((m) => `${m.role === "user" ? "Them" : "You"}: ${m.content}`).join("\n\n");
21386
+ return `YOUR READ OF THIS PERSON (the rubric + your grades + their receipts):
21387
+ ${context}
21388
+
21389
+ --- the conversation so far ---
21390
+ ${history}
21391
+
21392
+ Write your next reply to them now.`;
21393
+ }
21394
+
21395
+ // src/codingChat.ts
21396
+ async function handleCodingChat(profile, dataDir, body, res) {
21397
+ const raw = Array.isArray(body.messages) ? body.messages : [];
21398
+ const messages = raw.filter((m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string").slice(-20);
21399
+ if (!messages.length) {
21400
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
21401
+ res.end(JSON.stringify({ error: "no messages" }));
21402
+ return;
21403
+ }
21404
+ const prompt = buildCodingChatPrompt(profile, messages);
21405
+ res.writeHead(200, {
21406
+ "Content-Type": "application/x-ndjson; charset=utf-8",
21407
+ "Cache-Control": "no-store, no-transform",
21408
+ "X-Accel-Buffering": "no"
21409
+ });
21410
+ const send = (o) => {
21411
+ try {
21412
+ res.write(JSON.stringify(o) + "\n");
21413
+ } catch {
21414
+ }
21415
+ };
21416
+ try {
21417
+ const dataMap = await readDataMap();
21418
+ const run3 = await invokeAgent({
21419
+ prompt,
21420
+ systemPrompt: `${CODING_CHAT_SYSTEM}
21421
+
21422
+ AGENT MODE: you also have read-only tools (Read/Grep/Glob) over the person's local record. Use them whenever the pre-baked context can't answer precisely \u2014 the map below tells you what lives where. Traverse first, then answer with receipts. Never claim you lack access to something the map covers.
21423
+
21424
+ ${dataMap}`,
21425
+ addDir: dataDir,
21426
+ allowedTools: ["Read", "Grep", "Glob"],
21427
+ maxTurns: 16,
21428
+ model: "sonnet",
21429
+ onText: (delta) => send({ t: "delta", v: delta })
21430
+ });
21431
+ send({ t: "done", reply: stripSlop(run3.raw || "") || "(no response)" });
21432
+ } catch {
21433
+ send({ t: "error", message: "Couldn't reach your local Claude \u2014 is Claude Code (or Codex) installed and logged in?" });
21434
+ } finally {
21435
+ res.end();
21436
+ }
21437
+ }
21438
+
21240
21439
  // ../../lib/agents/engine/public-report.ts
21241
21440
  import { promises as fs19 } from "fs";
21242
21441
  import path23 from "path";
@@ -21785,9 +21984,17 @@ Apply the user's LAST instruction and return the complete updated payload.`;
21785
21984
  }
21786
21985
 
21787
21986
  // src/dataHome.ts
21788
- import { existsSync as existsSync4 } from "fs";
21987
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
21789
21988
  import os6 from "os";
21790
21989
  import path26 from "path";
21990
+ function hasCompletedAnalysis(root) {
21991
+ try {
21992
+ const a = JSON.parse(readFileSync2(path26.join(root, "coding", "aggregate.json"), "utf-8"));
21993
+ return !!String(a.bigPicture ?? "").trim();
21994
+ } catch {
21995
+ return false;
21996
+ }
21997
+ }
21791
21998
  function electronAppData() {
21792
21999
  const candidates = [
21793
22000
  "/Applications/Polymath.app/Contents/Resources/standalone/.data",
@@ -21796,14 +22003,18 @@ function electronAppData() {
21796
22003
  for (const c of candidates) if (existsSync4(path26.join(c, "coding"))) return c;
21797
22004
  return null;
21798
22005
  }
21799
- function resolveDataRoot(env = process.env, cwd = process.cwd(), appData = electronAppData) {
22006
+ function resolveDataRoot(env = process.env, cwd = process.cwd(), appData = electronAppData, home = os6.homedir()) {
21800
22007
  const fromEnv = env.POLYMATH_DATA_DIR;
21801
22008
  if (fromEnv && fromEnv.trim()) return path26.resolve(fromEnv);
21802
22009
  const local = path26.join(cwd, ".data");
21803
- if (existsSync4(local)) return local;
22010
+ const dflt = path26.join(home, ".polymath-society", "data");
22011
+ if (existsSync4(local) && hasCompletedAnalysis(local)) return local;
21804
22012
  const fromApp = appData();
22013
+ if (fromApp && hasCompletedAnalysis(fromApp)) return fromApp;
22014
+ if (hasCompletedAnalysis(dflt)) return dflt;
22015
+ if (existsSync4(local)) return local;
21805
22016
  if (fromApp) return fromApp;
21806
- return path26.join(os6.homedir(), ".polymath-society", "data");
22017
+ return dflt;
21807
22018
  }
21808
22019
 
21809
22020
  // src/manifest.ts
@@ -21879,6 +22090,8 @@ var EXPORT_LINKS = [
21879
22090
  // src/publicCodingPageApi.ts
21880
22091
  import { promises as fs28 } from "fs";
21881
22092
  import path32 from "path";
22093
+ import os7 from "os";
22094
+ import crypto3 from "crypto";
21882
22095
 
21883
22096
  // ../../lib/agents/coding/publicPage.ts
21884
22097
  import { promises as fs27 } from "fs";
@@ -22354,6 +22567,18 @@ function distOf3(takes) {
22354
22567
  const hist = [...h.keys()].sort((a, b) => b - a).map((level) => ({ level, count: h.get(level) }));
22355
22568
  return { n, observed, mean: mean2, median: median2, hist };
22356
22569
  }
22570
+ var phi = (z) => {
22571
+ const t = 1 / (1 + 0.2316419 * Math.abs(z));
22572
+ const d = 0.3989423 * Math.exp(-z * z / 2);
22573
+ let pr = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))));
22574
+ if (z > 0) pr = 1 - pr;
22575
+ return pr;
22576
+ };
22577
+ var lognPct = (x, c) => x == null || !c || x <= 0 ? null : Math.min(99.9, Math.max(0.1, Math.round(phi(Math.log(x / c.median) / c.sigma) * 1e3) / 10));
22578
+ var NO_GRADES_GAP = "no sessions graded for this yet \u2014 this fills in once grading has run";
22579
+ function estimateRowParts(score, nTakes, evidenceGap) {
22580
+ return nTakes === 0 && score == null ? { score: null, gap: NO_GRADES_GAP } : { score, gap: evidenceGap };
22581
+ }
22357
22582
  async function compileCodingProfile() {
22358
22583
  const allRecords = await readJson2(path30.join(CODING_DIR2, "sessions.json"), []);
22359
22584
  const dupIds = new Set(allRecords.filter((s) => s.duplicateOf).map((s) => s.sessionId));
@@ -22608,15 +22833,11 @@ async function compileCodingProfile() {
22608
22833
  const unb = ceilOf("unblocking");
22609
22834
  const agency = out2 != null && unb != null ? Math.round((out2 + unb) / 2) : out2 ?? unb;
22610
22835
  const sRow = (label, score, gap2) => ({ label, score, percentile: pctOf(score), gap: gap2 });
22611
- const tiers = DEFAULT_CALIBRATION.codingTiers;
22612
- const phi = (z) => {
22613
- const t = 1 / (1 + 0.2316419 * Math.abs(z));
22614
- const d = 0.3989423 * Math.exp(-z * z / 2);
22615
- let pr = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))));
22616
- if (z > 0) pr = 1 - pr;
22617
- return pr;
22836
+ const est = (label, score, key2, evidenceGap) => {
22837
+ const e = estimateRowParts(score, nTk(key2), evidenceGap);
22838
+ return sRow(label, e.score, e.gap);
22618
22839
  };
22619
- const lognPct = (x, c) => x == null || !c || x <= 0 ? null : Math.round(phi(Math.log(x / c.median) / c.sigma) * 1e3) / 10;
22840
+ const tiers = DEFAULT_CALIBRATION.codingTiers;
22620
22841
  const avgWords = aggregate?.throughput?.avgWordsPerDay ?? null;
22621
22842
  const pushPctl = lognPct(avgWords, tiers?.push);
22622
22843
  const focusPctl = lognPct(flowPctDay, tiers?.focus);
@@ -22635,10 +22856,16 @@ async function compileCodingProfile() {
22635
22856
  null,
22636
22857
  workDayHrs != null ? `~${workDayHrs}h of active building on a typical work day \u2014 engaged builders average ~3h, and sustaining 7h+ day after day, week after week, is top-1% territory` : "not enough dated activity yet to measure a typical work day"
22637
22858
  ),
22859
+ // The copy FOLLOWS the person's actual orchestration band (orchPctl above) —
22860
+ // a hardcoded "single biggest speed-up still on the table" once shipped to a
22861
+ // user at orchPctl 99.5 (18 agents peak, 48% of hours at 2+), flatly
22862
+ // contradicting the gap section's "already strong" on the same page
22863
+ // (2026-07-17). The still-on-the-table claim is reserved for genuinely low
22864
+ // orchestration.
22638
22865
  sRow(
22639
22866
  "Your AI parallelism",
22640
22867
  ceilOf("delegation"),
22641
- soloPct != null ? `~${soloPct}% of your hours you drive a single agent (${Math.round(soloMin / 60)}h solo vs ${Math.round(parMin / 60)}h with 2+ live) \u2014 the Claude Code lead keeps 10\u201315 going at once, so this is the single biggest speed-up still on the table` : "you brief the AI sharply, but mostly drive one agent at a time \u2014 the top run 10\u201315 in parallel, a several-fold speed-up"
22868
+ soloPct != null ? orchPctl >= 97 ? `you already run parallel at the frontier \u2014 ${Math.round(parMin / 60)}h of your hours with 2+ agents live vs ${Math.round(soloMin / 60)}h solo; the Claude Code lead keeps 10\u201315 going at once, and you work the same way` : `~${soloPct}% of your hours you drive a single agent (${Math.round(soloMin / 60)}h solo vs ${Math.round(parMin / 60)}h with 2+ live) \u2014 the Claude Code lead keeps 10\u201315 going at once, so this is the single biggest speed-up still on the table` : "you brief the AI sharply, but mostly drive one agent at a time \u2014 the top run 10\u201315 in parallel, a several-fold speed-up"
22642
22869
  ),
22643
22870
  // The evidence line cites the RANKING metric (flow as a share of the work
22644
22871
  // day), never absolute hours — a friend with MORE flow hours over a longer
@@ -22654,22 +22881,25 @@ async function compileCodingProfile() {
22654
22881
  // The four estimates LEAD with a concrete moment from their own sessions, then an
22655
22882
  // HONEST caveat — how many graded sessions back the read. No invented weakness, no
22656
22883
  // claim about what they "didn't" do (false specifics churn a real user); it's an estimate.
22657
- sRow(
22884
+ est(
22658
22885
  "Agency",
22659
22886
  agency,
22887
+ "outcomes",
22660
22888
  `${proud("outcomes", "you ship working systems and unblock yourself")} \u2014 one of ${nTk("outcomes")} sessions behind this read; a strong, consistent signal, still an estimate from your logs`
22661
22889
  ),
22662
22890
  // Judgement/Taste use the SAME numbers their ability cards prove (aggregate
22663
22891
  // peak-with-consistency over near-peak instances) — a chip saying "top 1%"
22664
22892
  // above a card proving 10/Top 0.1% with ten receipts reads as a bug.
22665
- sRow(
22893
+ est(
22666
22894
  "Judgement",
22667
22895
  aggregate?.ability?.abstraction?.score ?? ceilOf("generative"),
22896
+ "generative",
22668
22897
  `${proud("generative", "you make sharp, non-obvious calls on what actually matters")} \u2014 one of ${nTk("generative")} sessions we've graded for this; clear so far, not yet a final verdict`
22669
22898
  ),
22670
- sRow(
22899
+ est(
22671
22900
  "Taste",
22672
22901
  aggregate?.ability?.taste?.score ?? ceilOf("taste"),
22902
+ "taste",
22673
22903
  `${proud("taste", "you hold a high, opinionated quality bar")} \u2014 one of ${nTk("taste")} sessions behind this read; a real signal, still an estimate`
22674
22904
  )
22675
22905
  // Expertise: NO chip — the scored criterion is deprecated; the real judge is
@@ -23278,6 +23508,49 @@ async function handleAnalysisShare(dataDir, kind, full) {
23278
23508
  return { status: 502, json: { error: e.message.slice(0, 200) } };
23279
23509
  }
23280
23510
  }
23511
+ function computeMachineUnion(machines) {
23512
+ const counts = /* @__PURE__ */ new Map();
23513
+ for (const m of Object.values(machines)) for (const s of m.sessions ?? []) counts.set(s.id, (counts.get(s.id) ?? 0) + 1);
23514
+ return {
23515
+ machines: Object.entries(machines).map(([id, m]) => ({ id, label: m.label ?? id, sessions: (m.sessions ?? []).length, syncedAt: m.syncedAt ?? null })),
23516
+ uniqueSessions: counts.size,
23517
+ overlappingSessions: [...counts.values()].filter((n) => n > 1).length
23518
+ };
23519
+ }
23520
+ async function handleAnalysisSync(dataDir) {
23521
+ const cfg = centralConfig();
23522
+ if (!cfg.configured) return { status: 503, json: { error: "central services are disabled in this build" } };
23523
+ const auth = await getAccessToken(dataDir);
23524
+ if (!auth) return { status: 401, json: { error: "sign in with Google (top right) first" } };
23525
+ const mFile = path32.join(dataDir, "machine.json");
23526
+ let machine = await readJson4(mFile, null);
23527
+ if (!machine?.id) {
23528
+ machine = { id: crypto3.randomBytes(8).toString("hex"), label: os7.hostname().replace(/\.local$/, "") };
23529
+ await fs28.writeFile(mFile, JSON.stringify(machine, null, 2));
23530
+ }
23531
+ const rows = await readJson4(
23532
+ path32.join(dataDir, "coding", "sessions.json"),
23533
+ []
23534
+ );
23535
+ const sessions = rows.filter((r) => r.klass === "interactive" && !r.duplicateOf && r.sessionId).map((r) => ({ id: r.sessionId, start: r.start?.slice(0, 10) ?? null, chars: r.humanChars ?? 0, project: r.project ?? null, source: r.source ?? null }));
23536
+ if (!sessions.length) return { status: 409, json: { error: "nothing to sync yet \u2014 run the analysis first" } };
23537
+ const prior = await readOwnReportContent(cfg, auth.token, auth.identity.userId);
23538
+ try {
23539
+ const machines = prior.machines && typeof prior.machines === "object" ? prior.machines : {};
23540
+ machines[machine.id] = { label: machine.label, syncedAt: (/* @__PURE__ */ new Date()).toISOString(), sessions };
23541
+ const combined = computeMachineUnion(machines);
23542
+ const merged = { format: "coding-pr1", ...prior, machines, combined };
23543
+ const up = await fetch(`${cfg.supabaseUrl}/rest/v1/rpc/submit_report`, {
23544
+ method: "POST",
23545
+ headers: { apikey: cfg.supabaseAnonKey, authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
23546
+ body: JSON.stringify({ p_session_id: null, p_content: JSON.stringify(merged) })
23547
+ });
23548
+ if (!up.ok) return { status: 502, json: { error: `sync failed (HTTP ${up.status})` } };
23549
+ return { status: 200, json: { ok: true, machine: machine.label, ...combined } };
23550
+ } catch (e) {
23551
+ return { status: 502, json: { error: e.message.slice(0, 200) } };
23552
+ }
23553
+ }
23281
23554
 
23282
23555
  // src/server.ts
23283
23556
  var __dirname = path33.dirname(fileURLToPath2(import.meta.url));
@@ -23605,6 +23878,11 @@ function startServer(opts) {
23605
23878
  json(res, out.status, out.json);
23606
23879
  return;
23607
23880
  }
23881
+ if (req.method === "POST" && route === "/api/analysis/sync") {
23882
+ const out = await handleAnalysisSync(dataDir);
23883
+ json(res, out.status, out.json);
23884
+ return;
23885
+ }
23608
23886
  if (req.method === "POST" && route === "/api/analysis/share") {
23609
23887
  const body = JSON.parse(await readBody(req) || "{}");
23610
23888
  const out = await handleAnalysisShare(dataDir, body.kind === "chat" ? "chat" : "coding", {