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/cli.js CHANGED
@@ -33,9 +33,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
33
33
  ));
34
34
 
35
35
  // src/dataHome.ts
36
- import { existsSync } from "fs";
36
+ import { existsSync, readFileSync } from "fs";
37
37
  import os from "os";
38
38
  import path from "path";
39
+ function hasCompletedAnalysis(root) {
40
+ try {
41
+ const a = JSON.parse(readFileSync(path.join(root, "coding", "aggregate.json"), "utf-8"));
42
+ return !!String(a.bigPicture ?? "").trim();
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
39
47
  function electronAppData() {
40
48
  const candidates = [
41
49
  "/Applications/Polymath.app/Contents/Resources/standalone/.data",
@@ -44,14 +52,18 @@ function electronAppData() {
44
52
  for (const c of candidates) if (existsSync(path.join(c, "coding"))) return c;
45
53
  return null;
46
54
  }
47
- function resolveDataRoot(env = process.env, cwd = process.cwd(), appData = electronAppData) {
55
+ function resolveDataRoot(env = process.env, cwd = process.cwd(), appData = electronAppData, home = os.homedir()) {
48
56
  const fromEnv = env.POLYMATH_DATA_DIR;
49
57
  if (fromEnv && fromEnv.trim()) return path.resolve(fromEnv);
50
58
  const local = path.join(cwd, ".data");
51
- if (existsSync(local)) return local;
59
+ const dflt = path.join(home, ".polymath-society", "data");
60
+ if (existsSync(local) && hasCompletedAnalysis(local)) return local;
52
61
  const fromApp = appData();
62
+ if (fromApp && hasCompletedAnalysis(fromApp)) return fromApp;
63
+ if (hasCompletedAnalysis(dflt)) return dflt;
64
+ if (existsSync(local)) return local;
53
65
  if (fromApp) return fromApp;
54
- return path.join(os.homedir(), ".polymath-society", "data");
66
+ return dflt;
55
67
  }
56
68
  var init_dataHome = __esm({
57
69
  "src/dataHome.ts"() {
@@ -98,6 +110,103 @@ var init_injected = __esm({
98
110
  }
99
111
  });
100
112
 
113
+ // ../coding-core/dist/codex.js
114
+ function isCodexInjectedUserText(text2) {
115
+ return CODEX_INJECTED_TEXT_RE.test(text2);
116
+ }
117
+ function codexContentText(content) {
118
+ if (typeof content === "string")
119
+ return content;
120
+ if (!Array.isArray(content))
121
+ return "";
122
+ const parts = [];
123
+ for (const item of content) {
124
+ if (typeof item === "string") {
125
+ parts.push(item);
126
+ continue;
127
+ }
128
+ if (item && typeof item === "object") {
129
+ const it = item;
130
+ if (it.type === "input_text" || it.type === "output_text" || it.type === "text")
131
+ parts.push(it.text || "");
132
+ }
133
+ }
134
+ return parts.join("\n");
135
+ }
136
+ function sniffCodexRaw(raw) {
137
+ let checked = 0;
138
+ for (const lineRaw of raw.split("\n")) {
139
+ const line = lineRaw.trim();
140
+ if (!line)
141
+ continue;
142
+ if (checked++ >= 5)
143
+ break;
144
+ try {
145
+ const e = JSON.parse(line);
146
+ if (e.type === "session_meta" || e.type === "response_item" || e.type === "turn_context")
147
+ return true;
148
+ if (e.type === "user" || e.type === "assistant" || e.type === "summary" || e.type === "queue-operation")
149
+ return false;
150
+ } catch {
151
+ }
152
+ }
153
+ return false;
154
+ }
155
+ function extractCodexEvents(raw) {
156
+ const out = [];
157
+ for (const lineRaw of raw.split("\n")) {
158
+ const line = lineRaw.trim();
159
+ if (!line)
160
+ continue;
161
+ let e;
162
+ try {
163
+ e = JSON.parse(line);
164
+ } catch {
165
+ continue;
166
+ }
167
+ if (e.type !== "response_item")
168
+ continue;
169
+ const p = e.payload ?? {};
170
+ const t = typeof e.timestamp === "string" ? Date.parse(e.timestamp) : NaN;
171
+ const ptype = p.type;
172
+ if (ptype === "message") {
173
+ const role = p.role;
174
+ const text2 = codexContentText(p.content).replace(SYSTEM_REMINDER_RE2, "").trim();
175
+ if (!text2)
176
+ continue;
177
+ if (role === "user") {
178
+ if (isCodexInjectedUserText(text2))
179
+ continue;
180
+ out.push({ kind: "user", t, text: text2 });
181
+ } else if (role === "assistant") {
182
+ out.push({ kind: "assistant", t, text: text2 });
183
+ }
184
+ continue;
185
+ }
186
+ if (ptype === "function_call" || ptype === "custom_tool_call" || ptype === "local_shell_call") {
187
+ const name17 = typeof p.name === "string" && p.name ? p.name : ptype === "local_shell_call" ? "shell" : String(ptype);
188
+ const input = typeof p.arguments === "string" ? p.arguments : typeof p.input === "string" ? p.input : "";
189
+ out.push({ kind: "tool", t, name: name17, input });
190
+ continue;
191
+ }
192
+ if (ptype === "function_call_output" || ptype === "custom_tool_call_output") {
193
+ const output = typeof p.output === "string" ? p.output : "";
194
+ const m = EXIT_CODE_RE.exec(output.slice(0, 200));
195
+ out.push({ kind: "tool_output", t, output, isError: !!m && m[1] !== "0" });
196
+ }
197
+ }
198
+ return out;
199
+ }
200
+ var SYSTEM_REMINDER_RE2, CODEX_INJECTED_TEXT_RE, EXIT_CODE_RE;
201
+ var init_codex = __esm({
202
+ "../coding-core/dist/codex.js"() {
203
+ "use strict";
204
+ SYSTEM_REMINDER_RE2 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
205
+ CODEX_INJECTED_TEXT_RE = /^\s*(<(environment_context|user_instructions|ENVIRONMENT_CONTEXT|turn_aborted)\b|#\s*AGENTS\.md instructions\b)/;
206
+ EXIT_CODE_RE = /(?:Process exited with code|Exit code:)\s+(\d+)/;
207
+ }
208
+ });
209
+
101
210
  // ../coding-core/dist/raw.js
102
211
  import { promises as fs } from "fs";
103
212
  import path2 from "path";
@@ -370,7 +479,7 @@ function parseCodex(raw, file2) {
370
479
  const role = p.role;
371
480
  const text2 = codexText(p.content).replace(SYSTEM_REMINDER_RE, "").trim();
372
481
  if (role === "user") {
373
- if (text2 && !CODEX_INJECTED_RE.test(text2)) {
482
+ if (text2 && !isCodexInjectedUserText(text2)) {
374
483
  s.humanTurns++;
375
484
  s.humanChars += text2.length;
376
485
  if (!s.firstHuman)
@@ -762,11 +871,12 @@ async function readAllSessions(opts = {}) {
762
871
  demoteTemplateFanouts(all);
763
872
  return all;
764
873
  }
765
- var SOURCE_ROOT_ENV_VARS, anyRootOverridden, claudeProjectsRoot, codexSessionsRoot, cursorWorkspaceRoot, cursorProjectsRoot, SKIP_DIR_RE, AGENT_CWD_RE, PERSONA_RE, OUTPUT_CONTRACT_RES, CAPS_HEADER_RE, PING_RE, ms, CODEX_INJECTED_RE, CURSOR_USER_QUERY_RE, NODE_SQLITE, FANOUT_PREFIX, FANOUT_MIN_SESSIONS;
874
+ var SOURCE_ROOT_ENV_VARS, anyRootOverridden, claudeProjectsRoot, codexSessionsRoot, cursorWorkspaceRoot, cursorProjectsRoot, SKIP_DIR_RE, AGENT_CWD_RE, PERSONA_RE, OUTPUT_CONTRACT_RES, CAPS_HEADER_RE, PING_RE, ms, CURSOR_USER_QUERY_RE, NODE_SQLITE, FANOUT_PREFIX, FANOUT_MIN_SESSIONS;
766
875
  var init_raw = __esm({
767
876
  "../coding-core/dist/raw.js"() {
768
877
  "use strict";
769
878
  init_injected();
879
+ init_codex();
770
880
  SOURCE_ROOT_ENV_VARS = [
771
881
  "CLAUDE_PROJECTS_DIR",
772
882
  "CODEX_SESSIONS_DIR",
@@ -795,7 +905,6 @@ var init_raw = __esm({
795
905
  const n = Date.parse(ts);
796
906
  return Number.isFinite(n) ? n : null;
797
907
  };
798
- CODEX_INJECTED_RE = /^\s*<(environment_context|user_instructions)>/;
799
908
  CURSOR_USER_QUERY_RE = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/;
800
909
  NODE_SQLITE = "node:sqlite";
801
910
  FANOUT_PREFIX = 80;
@@ -1474,6 +1583,11 @@ async function readManifest(dataDir) {
1474
1583
  exports: { included: arr(ex.included), excluded: arr(ex.excluded) }
1475
1584
  };
1476
1585
  }
1586
+ function agentFlagsFromManifest(m) {
1587
+ if (!m?.agents) return { claudeCode: true, codex: true };
1588
+ const on = (id) => m.agents.included.includes(id);
1589
+ return { claudeCode: on("claude-code"), codex: on("codex") };
1590
+ }
1477
1591
  async function writeManifest(dataDir, m) {
1478
1592
  await fs22.mkdir(dataDir, { recursive: true });
1479
1593
  await fs22.writeFile(manifestPath(dataDir), JSON.stringify(m, null, 2));
@@ -2042,7 +2156,7 @@ __export(plan_exports, {
2042
2156
  tierFromStrings: () => tierFromStrings
2043
2157
  });
2044
2158
  import { promises as fs34 } from "fs";
2045
- import os9 from "os";
2159
+ import os10 from "os";
2046
2160
  import path39 from "path";
2047
2161
  function tierFromStrings(...hints) {
2048
2162
  for (const h of hints) {
@@ -2063,7 +2177,7 @@ function tierFromChatgptPlan(planType) {
2063
2177
  if (s === "free") return "gpt_free";
2064
2178
  return "unknown";
2065
2179
  }
2066
- async function detectCodexPlan(home = os9.homedir()) {
2180
+ async function detectCodexPlan(home = os10.homedir()) {
2067
2181
  try {
2068
2182
  const raw = JSON.parse(await fs34.readFile(path39.join(home, ".codex", "auth.json"), "utf8"));
2069
2183
  for (const tok of [raw.tokens?.id_token, raw.tokens?.access_token]) {
@@ -2079,7 +2193,7 @@ async function detectCodexPlan(home = os9.homedir()) {
2079
2193
  }
2080
2194
  return "unknown";
2081
2195
  }
2082
- async function detectPlan(home = os9.homedir()) {
2196
+ async function detectPlan(home = os10.homedir()) {
2083
2197
  let tier = "unknown";
2084
2198
  try {
2085
2199
  const raw = JSON.parse(await fs34.readFile(path39.join(home, ".claude.json"), "utf8"));
@@ -2170,17 +2284,18 @@ var init_plan = __esm({
2170
2284
  var wizard_exports = {};
2171
2285
  __export(wizard_exports, {
2172
2286
  runWizard: () => runWizard,
2173
- sayExportLinks: () => sayExportLinks
2287
+ sayExportLinks: () => sayExportLinks,
2288
+ scanJsonlDir: () => scanJsonlDir
2174
2289
  });
2175
2290
  import { promises as fs35 } from "fs";
2176
- import os10 from "os";
2291
+ import os11 from "os";
2177
2292
  import path40 from "path";
2178
2293
  import * as readline from "node:readline/promises";
2179
- async function scanJsonlDir(label, dir) {
2294
+ async function scanJsonlDir(label, dir, maxDepth = 1) {
2180
2295
  let files = 0;
2181
2296
  let newest = 0;
2182
2297
  async function walk(d, depth) {
2183
- if (depth > 1) return;
2298
+ if (depth > maxDepth) return;
2184
2299
  let names = [];
2185
2300
  try {
2186
2301
  names = await fs35.readdir(d);
@@ -2305,15 +2420,15 @@ async function runWizard() {
2305
2420
  });
2306
2421
  say(bold(" Step 1 \xB7 Your coding agents"));
2307
2422
  const roots = sourceRoots();
2308
- const scanRoot = (label, dir) => dir ? scanJsonlDir(label, dir) : Promise.resolve({ label, dir: "(disabled \u2014 log roots redirected via env)", files: 0, newest: null });
2309
- const [cc, cx] = await Promise.all([scanRoot("Claude Code", roots.claudeProjects), scanRoot("Codex", roots.codexSessions)]);
2423
+ const scanRoot = (label, dir, maxDepth) => dir ? scanJsonlDir(label, dir, maxDepth) : Promise.resolve({ label, dir: "(disabled \u2014 log roots redirected via env)", files: 0, newest: null });
2424
+ const [cc, cx] = await Promise.all([scanRoot("Claude Code", roots.claudeProjects, 1), scanRoot("Codex", roots.codexSessions, 3)]);
2310
2425
  const agentIds = ["claude-code", "codex"];
2311
2426
  const agentScans = [cc, cx];
2312
2427
  const present = agentIds.filter((_, i) => agentScans[i].files > 0);
2313
2428
  for (let i = 0; i < agentScans.length; i++) {
2314
2429
  const s = agentScans[i];
2315
2430
  const idx = present.indexOf(agentIds[i]);
2316
- say(s.files > 0 ? ` ${idx + 1}. ${green("\u2713")} ${bold(s.label)} \u2014 ${s.files.toLocaleString()} log files \xB7 newest ${s.newest} ${dim(s.dir.replace(os10.homedir(), "~"))}` : ` ${dim("\xB7")} ${dim(`${s.label} \u2014 none found`)} ${dim(s.dir.replace(os10.homedir(), "~"))}`);
2431
+ say(s.files > 0 ? ` ${idx + 1}. ${green("\u2713")} ${bold(s.label)} \u2014 ${s.files.toLocaleString()} log files \xB7 newest ${s.newest} ${dim(s.dir.replace(os11.homedir(), "~"))}` : ` ${dim("\xB7")} ${dim(`${s.label} \u2014 none found`)} ${dim(s.dir.replace(os11.homedir(), "~"))}`);
2317
2432
  }
2318
2433
  say(dim(" (log files include forks and agent runs \u2014 the report separates your real sessions out)"));
2319
2434
  if (present.length === 0) {
@@ -2376,17 +2491,20 @@ async function runWizard() {
2376
2491
  }
2377
2492
  };
2378
2493
  say(dim(" Scanning for known export formats (Spotlight + the usual folders, a few seconds)..."));
2379
- for (const c of await deterministicDiscover(os10.homedir())) add(await identifyPath(c));
2380
- for (const f of await scanForExports(path40.join(os10.homedir(), "Downloads"))) add(f);
2381
- for (const f of await scanForObsidianVaults(os10.homedir())) add(f);
2494
+ for (const c of await deterministicDiscover(os11.homedir())) add(await identifyPath(c));
2495
+ for (const f of await scanForExports(path40.join(os11.homedir(), "Downloads"))) add(f);
2496
+ for (const f of await scanForObsidianVaults(os11.homedir())) add(f);
2382
2497
  for (const f of [...previous?.exports.included ?? [], ...previous?.exports.excluded ?? []]) add(f);
2498
+ let linksShown = false;
2383
2499
  if (!found.length) {
2384
2500
  say(dim(" Nothing found in the usual places."));
2501
+ await sayExportLinks(say, /* @__PURE__ */ new Set());
2502
+ linksShown = true;
2385
2503
  if (await yes(` Search deeper with your own local agent? ${dim("(capped at ~90s)")}`, false)) {
2386
2504
  try {
2387
2505
  const { invokeAgent: invokeAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
2388
2506
  say(dim(" Searching (your agent writes its own filesystem searches; nothing leaves this machine; 90s max)..."));
2389
- const candidates = await agentDiscoverCandidates((inv) => invokeAgent3(inv), os10.homedir(), []);
2507
+ const candidates = await agentDiscoverCandidates((inv) => invokeAgent3(inv), os11.homedir(), []);
2390
2508
  for (const c of candidates) add(await identifyPath(c));
2391
2509
  } catch (e) {
2392
2510
  say(dim(` Agent search unavailable (${e instanceof Error ? e.message.slice(0, 80) : "failed"}).`));
@@ -2394,7 +2512,7 @@ async function runWizard() {
2394
2512
  }
2395
2513
  if (!found.length) {
2396
2514
  const folder = await ask(` Got them in a specific folder? ${dim("(path, or Enter to skip)")}: `);
2397
- if (folder) for (const f of await scanForExports(folder.replace(/^~(?=$|\/)/, os10.homedir()))) add(f);
2515
+ if (folder) for (const f of await scanForExports(folder.replace(/^~(?=$|\/)/, os11.homedir()))) add(f);
2398
2516
  }
2399
2517
  }
2400
2518
  if (found.length) {
@@ -2406,7 +2524,7 @@ async function runWizard() {
2406
2524
  const defaults = found.map(() => true);
2407
2525
  say(` Found ${dim("(identified locally from the files themselves)")}:`);
2408
2526
  found.forEach((f, i) => say(describeExport(f, i, defaults[i], hadPrevious)));
2409
- await sayExportLinks(say, new Set(found.map((f) => f.kind)));
2527
+ if (!linksShown) await sayExportLinks(say, new Set(found.map((f) => f.kind)));
2410
2528
  const include = await pick2(defaults, hadPrevious);
2411
2529
  const manifest = {
2412
2530
  approvedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2419,14 +2537,14 @@ async function runWizard() {
2419
2537
  approvedExports = manifest.exports.included;
2420
2538
  await writeManifest(dataDir, manifest);
2421
2539
  const nIn = manifest.exports.included.length;
2422
- say(` ${green("\u2713")} Saved to ${path40.join(dataDir, "exports-manifest.json").replace(os10.homedir(), "~")} \u2014 ${nIn} source${nIn === 1 ? "" : "s"} approved, ${manifest.exports.excluded.length} excluded (re-run anytime to change).`);
2540
+ say(` ${green("\u2713")} Saved to ${path40.join(dataDir, "exports-manifest.json").replace(os11.homedir(), "~")} \u2014 ${nIn} source${nIn === 1 ? "" : "s"} approved, ${manifest.exports.excluded.length} excluded (re-run anytime to change).`);
2423
2541
  for (const f of manifest.exports.excluded) say(` ${dim(`\u2717 ${path40.basename(f.path)} \u2014 excluded, never read past identification.`)}`);
2424
2542
  if (manifest.exports.included.some((f) => f.kind !== "unknown")) {
2425
2543
  say(` ${dim("These are ingested and analyzed locally when you pick the full analysis below.")}`);
2426
2544
  }
2427
2545
  } else {
2428
2546
  await writeManifest(dataDir, { approvedAt: (/* @__PURE__ */ new Date()).toISOString(), agents, exports: { included: [], excluded: [] } });
2429
- await sayExportLinks(say, /* @__PURE__ */ new Set());
2547
+ if (!linksShown) await sayExportLinks(say, /* @__PURE__ */ new Set());
2430
2548
  }
2431
2549
  {
2432
2550
  const { EXPORT_LINKS: EXPORT_LINKS2 } = await Promise.resolve().then(() => (init_exportLinks(), exportLinks_exports));
@@ -2941,6 +3059,7 @@ init_gate2();
2941
3059
 
2942
3060
  // ../coding-core/dist/messages.js
2943
3061
  init_injected();
3062
+ init_codex();
2944
3063
  import { promises as fs2 } from "fs";
2945
3064
  function extractText2(content) {
2946
3065
  if (typeof content === "string")
@@ -2952,6 +3071,22 @@ function extractText2(content) {
2952
3071
  function isToolResultOnly(content) {
2953
3072
  return Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
2954
3073
  }
3074
+ function extractUserMessagesCodex(raw) {
3075
+ const out = [];
3076
+ let lastTs = null;
3077
+ for (const ev of extractCodexEvents(raw)) {
3078
+ const t = ev.t;
3079
+ const gapMs = Number.isFinite(t) && lastTs != null ? t - lastTs : NaN;
3080
+ if (Number.isFinite(t))
3081
+ lastTs = t;
3082
+ if (ev.kind !== "user")
3083
+ continue;
3084
+ const text2 = humanTypedText(ev.text);
3085
+ if (text2 && Number.isFinite(t))
3086
+ out.push({ t, text: text2, gapMs });
3087
+ }
3088
+ return out.sort((a, b) => a.t - b.t);
3089
+ }
2955
3090
  async function extractUserMessages(file2) {
2956
3091
  let raw;
2957
3092
  try {
@@ -2959,6 +3094,8 @@ async function extractUserMessages(file2) {
2959
3094
  } catch {
2960
3095
  return [];
2961
3096
  }
3097
+ if (sniffCodexRaw(raw))
3098
+ return extractUserMessagesCodex(raw);
2962
3099
  const out = [];
2963
3100
  const userTexts = /* @__PURE__ */ new Set();
2964
3101
  const seenUuids = /* @__PURE__ */ new Set();
@@ -3013,6 +3150,49 @@ function createMessageDeduper() {
3013
3150
  return true;
3014
3151
  };
3015
3152
  }
3153
+ function extractMessagesCodex(raw) {
3154
+ const out = [];
3155
+ let aiProse = [];
3156
+ let aiTools = {};
3157
+ let aiStart = 0;
3158
+ const flushAI = () => {
3159
+ if (!aiProse.length && Object.keys(aiTools).length === 0)
3160
+ return;
3161
+ const toolStr = Object.entries(aiTools).map(([n, c]) => c > 1 ? `${n}\xD7${c}` : n).join(", ");
3162
+ let prose = aiProse.join(" ").replace(/\s+/g, " ").trim();
3163
+ if (prose.length > 2200)
3164
+ prose = prose.slice(0, 2200) + "\u2026";
3165
+ let text2 = prose;
3166
+ if (toolStr)
3167
+ text2 = (text2 ? text2 + " " : "") + `[ran ${toolStr}]`;
3168
+ if (text2)
3169
+ out.push({ t: aiStart, role: "ai", text: text2 });
3170
+ aiProse = [];
3171
+ aiTools = {};
3172
+ };
3173
+ for (const ev of extractCodexEvents(raw)) {
3174
+ if (!Number.isFinite(ev.t))
3175
+ continue;
3176
+ if (ev.kind === "user") {
3177
+ const text2 = humanTypedText(ev.text);
3178
+ if (text2) {
3179
+ flushAI();
3180
+ out.push({ t: ev.t, role: "user", text: text2 });
3181
+ }
3182
+ } else if (ev.kind === "assistant") {
3183
+ if (!aiProse.length && Object.keys(aiTools).length === 0)
3184
+ aiStart = ev.t;
3185
+ if (ev.text)
3186
+ aiProse.push(ev.text);
3187
+ } else if (ev.kind === "tool") {
3188
+ if (!aiProse.length && Object.keys(aiTools).length === 0)
3189
+ aiStart = ev.t;
3190
+ aiTools[ev.name] = (aiTools[ev.name] ?? 0) + 1;
3191
+ }
3192
+ }
3193
+ flushAI();
3194
+ return out.sort((a, b) => a.t - b.t);
3195
+ }
3016
3196
  async function extractMessages(file2) {
3017
3197
  let raw;
3018
3198
  try {
@@ -3020,6 +3200,8 @@ async function extractMessages(file2) {
3020
3200
  } catch {
3021
3201
  return [];
3022
3202
  }
3203
+ if (sniffCodexRaw(raw))
3204
+ return extractMessagesCodex(raw);
3023
3205
  const out = [];
3024
3206
  const userTexts = /* @__PURE__ */ new Set();
3025
3207
  const seenUuids = /* @__PURE__ */ new Set();
@@ -3755,7 +3937,7 @@ var LABEL_BY_UIKEY = Object.fromEntries(GRADED_CRITERIA2.map((c) => [c.uiKey, c.
3755
3937
  // src/grade/materialize.ts
3756
3938
  init_injected();
3757
3939
  import { promises as fs3 } from "fs";
3758
- var SYSTEM_REMINDER_RE2 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
3940
+ var SYSTEM_REMINDER_RE3 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
3759
3941
  var INJECT_GIST = 120;
3760
3942
  var INJECTED = [
3761
3943
  { re: /<task-notification>[\s\S]*?<\/task-notification>/g, label: "bg task", gistRe: /<summary>([\s\S]*?)<\/summary>/ },
@@ -3775,7 +3957,7 @@ function collapseInjected(input) {
3775
3957
  return "";
3776
3958
  });
3777
3959
  }
3778
- return { text: text2.replace(SYSTEM_REMINDER_RE2, "").trim(), markers };
3960
+ return { text: text2.replace(SYSTEM_REMINDER_RE3, "").trim(), markers };
3779
3961
  }
3780
3962
  function extractText3(content) {
3781
3963
  if (typeof content === "string") return content;
@@ -4447,7 +4629,7 @@ import { promises as fs14 } from "fs";
4447
4629
  import path19 from "path";
4448
4630
 
4449
4631
  // ../../lib/agents/shared/agent.ts
4450
- import { readFileSync } from "fs";
4632
+ import { readFileSync as readFileSync2 } from "fs";
4451
4633
  import path13 from "path";
4452
4634
 
4453
4635
  // ../../lib/agents/shared/cliAdapter.ts
@@ -19895,7 +20077,7 @@ var ADAPTERS2 = {
19895
20077
  };
19896
20078
  function preferredEngine() {
19897
20079
  try {
19898
- const p = JSON.parse(readFileSync(path13.join(process.cwd(), ".data", "profile.json"), "utf-8"));
20080
+ const p = JSON.parse(readFileSync2(path13.join(process.cwd(), ".data", "profile.json"), "utf-8"));
19899
20081
  const e = p?.preferredEngine;
19900
20082
  return e === "cli" || e === "codex" || e === "cursor" || e === "api" ? e : null;
19901
20083
  } catch {
@@ -21247,8 +21429,9 @@ import path17 from "node:path";
21247
21429
 
21248
21430
  // ../../lib/agents/coding/materialize.ts
21249
21431
  init_injected();
21432
+ init_codex();
21250
21433
  import { promises as fs11 } from "fs";
21251
- var SYSTEM_REMINDER_RE3 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
21434
+ var SYSTEM_REMINDER_RE4 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
21252
21435
  var INJECT_GIST2 = 120;
21253
21436
  var INJECTED2 = [
21254
21437
  { re: /<task-notification>[\s\S]*?<\/task-notification>/g, label: "bg task", gistRe: /<summary>([\s\S]*?)<\/summary>/ },
@@ -21268,7 +21451,7 @@ function collapseInjected2(input) {
21268
21451
  return "";
21269
21452
  });
21270
21453
  }
21271
- return { text: text2.replace(SYSTEM_REMINDER_RE3, "").trim(), markers };
21454
+ return { text: text2.replace(SYSTEM_REMINDER_RE4, "").trim(), markers };
21272
21455
  }
21273
21456
  function extractText4(content) {
21274
21457
  if (typeof content === "string") return content;
@@ -21285,6 +21468,38 @@ function isToolResultOnly3(content) {
21285
21468
  return Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
21286
21469
  }
21287
21470
  var AI_GIST2 = 160;
21471
+ function materializeCodex(raw) {
21472
+ const lines = [];
21473
+ let humanTurns = 0, humanChars = 0;
21474
+ let aiLastProse = "";
21475
+ const aiTools = /* @__PURE__ */ new Map();
21476
+ const flushAI = () => {
21477
+ if (!aiLastProse && aiTools.size === 0) return;
21478
+ const tools = [...aiTools.entries()].map(([n, c]) => c > 1 ? `${n}\xD7${c}` : n).join(", ");
21479
+ const gist = aiLastProse ? aiLastProse.length > AI_GIST2 ? aiLastProse.slice(0, AI_GIST2) + "\u2026" : aiLastProse : "";
21480
+ const bits = [];
21481
+ if (gist) bits.push(gist);
21482
+ if (tools) bits.push(`ran ${tools}`);
21483
+ lines.push(`[AI: ${bits.join(" \xB7 ")}]`);
21484
+ aiLastProse = "";
21485
+ aiTools.clear();
21486
+ };
21487
+ for (const ev of extractCodexEvents(raw)) {
21488
+ if (ev.kind === "user") {
21489
+ flushAI();
21490
+ humanTurns++;
21491
+ humanChars += ev.text.length;
21492
+ lines.push(`>> ${ev.text}`);
21493
+ } else if (ev.kind === "assistant") {
21494
+ const prose = ev.text.replace(/\s+/g, " ").trim();
21495
+ if (prose) aiLastProse = prose;
21496
+ } else if (ev.kind === "tool") {
21497
+ aiTools.set(ev.name, (aiTools.get(ev.name) ?? 0) + 1);
21498
+ }
21499
+ }
21500
+ flushAI();
21501
+ return { text: lines.join("\n"), humanTurns, humanChars };
21502
+ }
21288
21503
  async function materializeSession2(file2) {
21289
21504
  let raw;
21290
21505
  try {
@@ -21292,6 +21507,7 @@ async function materializeSession2(file2) {
21292
21507
  } catch {
21293
21508
  return null;
21294
21509
  }
21510
+ if (sniffCodexRaw(raw)) return materializeCodex(raw);
21295
21511
  const lines = [];
21296
21512
  let humanTurns = 0, humanChars = 0;
21297
21513
  const pendingQueued = [];
@@ -21827,124 +22043,6 @@ function handleDiagnostic(method, body, key2) {
21827
22043
  // src/codingChat.ts
21828
22044
  init_agent();
21829
22045
 
21830
- // ../../lib/agents/coding/chat-context.ts
21831
- var RUNGS_BY_KEY = new Map(CODING_CRITERIA.map((c) => [c.key, c.rungs ?? []]));
21832
- var clip = (s, n) => {
21833
- const t = (s || "").replace(/\s+/g, " ").trim();
21834
- return t.length > n ? t.slice(0, n - 1).replace(/\s+\S*$/, "") + "\u2026" : t;
21835
- };
21836
- function criterionBlock(c) {
21837
- if (!c.graded || c.dist.n <= 0) return null;
21838
- const lines = [];
21839
- const mean2 = c.dist.mean != null ? `${c.dist.mean}` : "\u2014";
21840
- lines.push(`### ${c.label} (your mean ${mean2} across ${c.dist.n} graded session${c.dist.n === 1 ? "" : "s"})`);
21841
- lines.push(`What it measures: ${clip(c.definition, 400)}`);
21842
- const rungs = RUNGS_BY_KEY.get(c.key) ?? [];
21843
- if (rungs.length) {
21844
- lines.push(`The ladder I grade against:`);
21845
- for (const r of rungs) lines.push(` L${r.level} \u2014 ${r.marker}`);
21846
- }
21847
- const takes = (c.takes || []).slice(0, 5);
21848
- if (takes.length) {
21849
- lines.push(`How I scored your sessions here:`);
21850
- for (const t of takes) {
21851
- const sc = t.score != null ? `${t.score}` : t.best != null ? `~${t.best}` : "\u2014";
21852
- const parts = [` \u2022 "${clip(t.title, 80)}" (${(t.date || "").slice(0, 10)}) \u2014 scored ${sc}${t.confidence ? ` (${t.confidence})` : ""}.`];
21853
- if (t.instance) parts.push(`What showed it: ${clip(t.instance, 240)}`);
21854
- if (t.why) parts.push(`Why that score: ${clip(t.why, 320)}`);
21855
- const qs = (t.quotes || []).filter(Boolean).slice(0, 2).map((q) => `"${clip(q, 160)}"`);
21856
- if (qs.length) parts.push(`You said: ${qs.join(" / ")}`);
21857
- lines.push(parts.join(" "));
21858
- }
21859
- }
21860
- return lines.join("\n");
21861
- }
21862
- function buildCodingContext(profile) {
21863
- const out = [];
21864
- const t = profile.throughput;
21865
- const p = profile.parallelism;
21866
- const meta = [];
21867
- if (t?.sessions != null) meta.push(`${t.sessions} interactive coding sessions`);
21868
- if (profile.gradedSessions != null) meta.push(`${profile.gradedSessions} graded`);
21869
- if (p?.maxConcurrency != null) meta.push(`peak ${p.maxConcurrency} sessions in parallel`);
21870
- if (meta.length) out.push(`HOW THEY WORK: ${meta.join(" \xB7 ")}.`);
21871
- const blocks = (profile.criteria || []).map(criterionBlock).filter((b) => !!b);
21872
- if (blocks.length) {
21873
- out.push(`
21874
- YOUR GRADED CRITERIA \u2014 the rubric ladder and how I scored you on each, with your own words as the receipts:`);
21875
- out.push(blocks.join("\n\n"));
21876
- } else {
21877
- out.push(`
21878
- (No criteria have been graded yet, so I can only speak to how you work, not the qualitative scores.)`);
21879
- }
21880
- return out.join("\n");
21881
- }
21882
- 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.
21883
-
21884
- HOW TO ANSWER:
21885
- - 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.
21886
- - 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.
21887
- - 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.
21888
- - 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.
21889
- - Keep replies tight and human unless they ask you to go deep. Output ONLY your reply \u2014 no preamble, no meta.
21890
-
21891
- Do NOT use em dashes. Use a comma or a period instead.`;
21892
- function stripSlop(raw) {
21893
- return (raw || "").trim().replace(/\s*—\s*/g, ", ").replace(/\s+–\s+/g, ", ").replace(/\s+,/g, ",");
21894
- }
21895
- function buildCodingChatPrompt(profile, messages) {
21896
- const context = buildCodingContext(profile);
21897
- const history = messages.map((m) => `${m.role === "user" ? "Them" : "You"}: ${m.content}`).join("\n\n");
21898
- return `YOUR READ OF THIS PERSON (the rubric + your grades + their receipts):
21899
- ${context}
21900
-
21901
- --- the conversation so far ---
21902
- ${history}
21903
-
21904
- Write your next reply to them now.`;
21905
- }
21906
-
21907
- // src/codingChat.ts
21908
- async function handleCodingChat(profile, dataDir, body, res) {
21909
- const raw = Array.isArray(body.messages) ? body.messages : [];
21910
- const messages = raw.filter((m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string").slice(-20);
21911
- if (!messages.length) {
21912
- res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
21913
- res.end(JSON.stringify({ error: "no messages" }));
21914
- return;
21915
- }
21916
- const prompt = buildCodingChatPrompt(profile, messages);
21917
- res.writeHead(200, {
21918
- "Content-Type": "application/x-ndjson; charset=utf-8",
21919
- "Cache-Control": "no-store, no-transform",
21920
- "X-Accel-Buffering": "no"
21921
- });
21922
- const send = (o) => {
21923
- try {
21924
- res.write(JSON.stringify(o) + "\n");
21925
- } catch {
21926
- }
21927
- };
21928
- try {
21929
- const run4 = await invokeAgent({
21930
- prompt,
21931
- systemPrompt: CODING_CHAT_SYSTEM,
21932
- // Everything it needs is in the prompt; dataDir is a harmless read-only
21933
- // sandbox. maxTurns 2 → answer directly, never a tool-loop.
21934
- addDir: dataDir,
21935
- allowedTools: ["Read"],
21936
- maxTurns: 2,
21937
- model: "sonnet",
21938
- onText: (delta) => send({ t: "delta", v: delta })
21939
- });
21940
- send({ t: "done", reply: stripSlop(run4.raw || "") || "(no response)" });
21941
- } catch {
21942
- send({ t: "error", message: "Couldn't reach your local Claude \u2014 is Claude Code (or Codex) installed and logged in?" });
21943
- } finally {
21944
- res.end();
21945
- }
21946
- }
21947
-
21948
22046
  // src/personApi.ts
21949
22047
  init_agent();
21950
22048
  import { promises as fs18 } from "fs";
@@ -22507,6 +22605,137 @@ ${dataMap}`,
22507
22605
  }
22508
22606
  }
22509
22607
 
22608
+ // ../../lib/agents/coding/chat-context.ts
22609
+ var RUNGS_BY_KEY = new Map(CODING_CRITERIA.map((c) => [c.key, c.rungs ?? []]));
22610
+ var clip = (s, n) => {
22611
+ const t = (s || "").replace(/\s+/g, " ").trim();
22612
+ return t.length > n ? t.slice(0, n - 1).replace(/\s+\S*$/, "") + "\u2026" : t;
22613
+ };
22614
+ function criterionBlock(c) {
22615
+ if (!c.graded || c.dist.n <= 0) return null;
22616
+ const lines = [];
22617
+ const mean2 = c.dist.mean != null ? `${c.dist.mean}` : "\u2014";
22618
+ lines.push(`### ${c.label} (your mean ${mean2} across ${c.dist.n} graded session${c.dist.n === 1 ? "" : "s"})`);
22619
+ lines.push(`What it measures: ${clip(c.definition, 400)}`);
22620
+ const rungs = RUNGS_BY_KEY.get(c.key) ?? [];
22621
+ if (rungs.length) {
22622
+ lines.push(`The ladder I grade against:`);
22623
+ for (const r of rungs) lines.push(` L${r.level} \u2014 ${r.marker}`);
22624
+ }
22625
+ const takes = (c.takes || []).slice(0, 5);
22626
+ if (takes.length) {
22627
+ lines.push(`How I scored your sessions here:`);
22628
+ for (const t of takes) {
22629
+ const sc = t.score != null ? `${t.score}` : t.best != null ? `~${t.best}` : "\u2014";
22630
+ const parts = [` \u2022 "${clip(t.title, 80)}" (${(t.date || "").slice(0, 10)}) \u2014 scored ${sc}${t.confidence ? ` (${t.confidence})` : ""}.`];
22631
+ if (t.instance) parts.push(`What showed it: ${clip(t.instance, 240)}`);
22632
+ if (t.why) parts.push(`Why that score: ${clip(t.why, 320)}`);
22633
+ const qs = (t.quotes || []).filter(Boolean).slice(0, 2).map((q) => `"${clip(q, 160)}"`);
22634
+ if (qs.length) parts.push(`You said: ${qs.join(" / ")}`);
22635
+ lines.push(parts.join(" "));
22636
+ }
22637
+ }
22638
+ return lines.join("\n");
22639
+ }
22640
+ 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):
22641
+ - 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).
22642
+ - 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.
22643
+ - coding/day-digest.json and coding/day-chart.json \u2014 per-day narratives and per-conversation active intervals.
22644
+ - coding/projects.json \u2014 per-repo attribution. coding/delegation/ \u2014 delegation pattern reads.
22645
+ - 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.
22646
+ - 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.
22647
+ 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.`;
22648
+ function buildCodingContext(profile) {
22649
+ const out = [];
22650
+ const t = profile.throughput;
22651
+ const p = profile.parallelism;
22652
+ const meta = [];
22653
+ if (t?.sessions != null) meta.push(`${t.sessions} interactive coding sessions`);
22654
+ if (profile.gradedSessions != null) meta.push(`${profile.gradedSessions} graded`);
22655
+ if (p?.maxConcurrency != null) meta.push(`peak ${p.maxConcurrency} sessions in parallel`);
22656
+ if (meta.length) out.push(`HOW THEY WORK: ${meta.join(" \xB7 ")}.`);
22657
+ const blocks = (profile.criteria || []).map(criterionBlock).filter((b) => !!b);
22658
+ if (blocks.length) {
22659
+ out.push(`
22660
+ YOUR GRADED CRITERIA \u2014 the rubric ladder and how I scored you on each, with your own words as the receipts:`);
22661
+ out.push(blocks.join("\n\n"));
22662
+ } else {
22663
+ out.push(`
22664
+ 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.`);
22665
+ }
22666
+ out.push(`
22667
+ ${CODING_DATA_HOME_MAP}`);
22668
+ return out.join("\n");
22669
+ }
22670
+ 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.
22671
+
22672
+ HOW TO ANSWER:
22673
+ - 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.
22674
+ - 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.
22675
+ - 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.
22676
+ - 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.
22677
+ - Keep replies tight and human unless they ask you to go deep. Output ONLY your reply \u2014 no preamble, no meta.
22678
+
22679
+ Do NOT use em dashes. Use a comma or a period instead.`;
22680
+ function stripSlop(raw) {
22681
+ return (raw || "").trim().replace(/\s*—\s*/g, ", ").replace(/\s+–\s+/g, ", ").replace(/\s+,/g, ",");
22682
+ }
22683
+ function buildCodingChatPrompt(profile, messages) {
22684
+ const context = buildCodingContext(profile);
22685
+ const history = messages.map((m) => `${m.role === "user" ? "Them" : "You"}: ${m.content}`).join("\n\n");
22686
+ return `YOUR READ OF THIS PERSON (the rubric + your grades + their receipts):
22687
+ ${context}
22688
+
22689
+ --- the conversation so far ---
22690
+ ${history}
22691
+
22692
+ Write your next reply to them now.`;
22693
+ }
22694
+
22695
+ // src/codingChat.ts
22696
+ async function handleCodingChat(profile, dataDir, body, res) {
22697
+ const raw = Array.isArray(body.messages) ? body.messages : [];
22698
+ const messages = raw.filter((m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string").slice(-20);
22699
+ if (!messages.length) {
22700
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
22701
+ res.end(JSON.stringify({ error: "no messages" }));
22702
+ return;
22703
+ }
22704
+ const prompt = buildCodingChatPrompt(profile, messages);
22705
+ res.writeHead(200, {
22706
+ "Content-Type": "application/x-ndjson; charset=utf-8",
22707
+ "Cache-Control": "no-store, no-transform",
22708
+ "X-Accel-Buffering": "no"
22709
+ });
22710
+ const send = (o) => {
22711
+ try {
22712
+ res.write(JSON.stringify(o) + "\n");
22713
+ } catch {
22714
+ }
22715
+ };
22716
+ try {
22717
+ const dataMap = await readDataMap();
22718
+ const run4 = await invokeAgent({
22719
+ prompt,
22720
+ systemPrompt: `${CODING_CHAT_SYSTEM}
22721
+
22722
+ 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.
22723
+
22724
+ ${dataMap}`,
22725
+ addDir: dataDir,
22726
+ allowedTools: ["Read", "Grep", "Glob"],
22727
+ maxTurns: 16,
22728
+ model: "sonnet",
22729
+ onText: (delta) => send({ t: "delta", v: delta })
22730
+ });
22731
+ send({ t: "done", reply: stripSlop(run4.raw || "") || "(no response)" });
22732
+ } catch {
22733
+ send({ t: "error", message: "Couldn't reach your local Claude \u2014 is Claude Code (or Codex) installed and logged in?" });
22734
+ } finally {
22735
+ res.end();
22736
+ }
22737
+ }
22738
+
22510
22739
  // src/publicReportApi.ts
22511
22740
  init_agent();
22512
22741
 
@@ -23066,6 +23295,8 @@ init_exportLinks();
23066
23295
  // src/publicCodingPageApi.ts
23067
23296
  import { promises as fs28 } from "fs";
23068
23297
  import path32 from "path";
23298
+ import os7 from "os";
23299
+ import crypto3 from "crypto";
23069
23300
 
23070
23301
  // ../../lib/agents/coding/publicPage.ts
23071
23302
  import { promises as fs27 } from "fs";
@@ -23541,6 +23772,18 @@ function distOf3(takes) {
23541
23772
  const hist = [...h.keys()].sort((a, b) => b - a).map((level) => ({ level, count: h.get(level) }));
23542
23773
  return { n, observed, mean: mean2, median: median2, hist };
23543
23774
  }
23775
+ var phi = (z) => {
23776
+ const t = 1 / (1 + 0.2316419 * Math.abs(z));
23777
+ const d = 0.3989423 * Math.exp(-z * z / 2);
23778
+ let pr = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))));
23779
+ if (z > 0) pr = 1 - pr;
23780
+ return pr;
23781
+ };
23782
+ 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));
23783
+ var NO_GRADES_GAP = "no sessions graded for this yet \u2014 this fills in once grading has run";
23784
+ function estimateRowParts(score, nTakes, evidenceGap) {
23785
+ return nTakes === 0 && score == null ? { score: null, gap: NO_GRADES_GAP } : { score, gap: evidenceGap };
23786
+ }
23544
23787
  async function compileCodingProfile() {
23545
23788
  const allRecords = await readJson2(path30.join(CODING_DIR2, "sessions.json"), []);
23546
23789
  const dupIds = new Set(allRecords.filter((s) => s.duplicateOf).map((s) => s.sessionId));
@@ -23795,15 +24038,11 @@ async function compileCodingProfile() {
23795
24038
  const unb = ceilOf("unblocking");
23796
24039
  const agency = out2 != null && unb != null ? Math.round((out2 + unb) / 2) : out2 ?? unb;
23797
24040
  const sRow = (label, score, gap2) => ({ label, score, percentile: pctOf(score), gap: gap2 });
23798
- const tiers = DEFAULT_CALIBRATION.codingTiers;
23799
- const phi = (z) => {
23800
- const t = 1 / (1 + 0.2316419 * Math.abs(z));
23801
- const d = 0.3989423 * Math.exp(-z * z / 2);
23802
- let pr = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))));
23803
- if (z > 0) pr = 1 - pr;
23804
- return pr;
24041
+ const est = (label, score, key2, evidenceGap) => {
24042
+ const e = estimateRowParts(score, nTk(key2), evidenceGap);
24043
+ return sRow(label, e.score, e.gap);
23805
24044
  };
23806
- const lognPct = (x, c) => x == null || !c || x <= 0 ? null : Math.round(phi(Math.log(x / c.median) / c.sigma) * 1e3) / 10;
24045
+ const tiers = DEFAULT_CALIBRATION.codingTiers;
23807
24046
  const avgWords = aggregate?.throughput?.avgWordsPerDay ?? null;
23808
24047
  const pushPctl = lognPct(avgWords, tiers?.push);
23809
24048
  const focusPctl = lognPct(flowPctDay, tiers?.focus);
@@ -23822,10 +24061,16 @@ async function compileCodingProfile() {
23822
24061
  null,
23823
24062
  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"
23824
24063
  ),
24064
+ // The copy FOLLOWS the person's actual orchestration band (orchPctl above) —
24065
+ // a hardcoded "single biggest speed-up still on the table" once shipped to a
24066
+ // user at orchPctl 99.5 (18 agents peak, 48% of hours at 2+), flatly
24067
+ // contradicting the gap section's "already strong" on the same page
24068
+ // (2026-07-17). The still-on-the-table claim is reserved for genuinely low
24069
+ // orchestration.
23825
24070
  sRow(
23826
24071
  "Your AI parallelism",
23827
24072
  ceilOf("delegation"),
23828
- 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"
24073
+ 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"
23829
24074
  ),
23830
24075
  // The evidence line cites the RANKING metric (flow as a share of the work
23831
24076
  // day), never absolute hours — a friend with MORE flow hours over a longer
@@ -23841,22 +24086,25 @@ async function compileCodingProfile() {
23841
24086
  // The four estimates LEAD with a concrete moment from their own sessions, then an
23842
24087
  // HONEST caveat — how many graded sessions back the read. No invented weakness, no
23843
24088
  // claim about what they "didn't" do (false specifics churn a real user); it's an estimate.
23844
- sRow(
24089
+ est(
23845
24090
  "Agency",
23846
24091
  agency,
24092
+ "outcomes",
23847
24093
  `${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`
23848
24094
  ),
23849
24095
  // Judgement/Taste use the SAME numbers their ability cards prove (aggregate
23850
24096
  // peak-with-consistency over near-peak instances) — a chip saying "top 1%"
23851
24097
  // above a card proving 10/Top 0.1% with ten receipts reads as a bug.
23852
- sRow(
24098
+ est(
23853
24099
  "Judgement",
23854
24100
  aggregate?.ability?.abstraction?.score ?? ceilOf("generative"),
24101
+ "generative",
23855
24102
  `${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`
23856
24103
  ),
23857
- sRow(
24104
+ est(
23858
24105
  "Taste",
23859
24106
  aggregate?.ability?.taste?.score ?? ceilOf("taste"),
24107
+ "taste",
23860
24108
  `${proud("taste", "you hold a high, opinionated quality bar")} \u2014 one of ${nTk("taste")} sessions behind this read; a real signal, still an estimate`
23861
24109
  )
23862
24110
  // Expertise: NO chip — the scored criterion is deprecated; the real judge is
@@ -24465,6 +24713,49 @@ async function handleAnalysisShare(dataDir, kind, full) {
24465
24713
  return { status: 502, json: { error: e.message.slice(0, 200) } };
24466
24714
  }
24467
24715
  }
24716
+ function computeMachineUnion(machines) {
24717
+ const counts = /* @__PURE__ */ new Map();
24718
+ for (const m of Object.values(machines)) for (const s of m.sessions ?? []) counts.set(s.id, (counts.get(s.id) ?? 0) + 1);
24719
+ return {
24720
+ machines: Object.entries(machines).map(([id, m]) => ({ id, label: m.label ?? id, sessions: (m.sessions ?? []).length, syncedAt: m.syncedAt ?? null })),
24721
+ uniqueSessions: counts.size,
24722
+ overlappingSessions: [...counts.values()].filter((n) => n > 1).length
24723
+ };
24724
+ }
24725
+ async function handleAnalysisSync(dataDir) {
24726
+ const cfg = centralConfig();
24727
+ if (!cfg.configured) return { status: 503, json: { error: "central services are disabled in this build" } };
24728
+ const auth = await getAccessToken(dataDir);
24729
+ if (!auth) return { status: 401, json: { error: "sign in with Google (top right) first" } };
24730
+ const mFile = path32.join(dataDir, "machine.json");
24731
+ let machine = await readJson4(mFile, null);
24732
+ if (!machine?.id) {
24733
+ machine = { id: crypto3.randomBytes(8).toString("hex"), label: os7.hostname().replace(/\.local$/, "") };
24734
+ await fs28.writeFile(mFile, JSON.stringify(machine, null, 2));
24735
+ }
24736
+ const rows = await readJson4(
24737
+ path32.join(dataDir, "coding", "sessions.json"),
24738
+ []
24739
+ );
24740
+ 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 }));
24741
+ if (!sessions.length) return { status: 409, json: { error: "nothing to sync yet \u2014 run the analysis first" } };
24742
+ const prior = await readOwnReportContent(cfg, auth.token, auth.identity.userId);
24743
+ try {
24744
+ const machines = prior.machines && typeof prior.machines === "object" ? prior.machines : {};
24745
+ machines[machine.id] = { label: machine.label, syncedAt: (/* @__PURE__ */ new Date()).toISOString(), sessions };
24746
+ const combined = computeMachineUnion(machines);
24747
+ const merged = { format: "coding-pr1", ...prior, machines, combined };
24748
+ const up = await fetch(`${cfg.supabaseUrl}/rest/v1/rpc/submit_report`, {
24749
+ method: "POST",
24750
+ headers: { apikey: cfg.supabaseAnonKey, authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
24751
+ body: JSON.stringify({ p_session_id: null, p_content: JSON.stringify(merged) })
24752
+ });
24753
+ if (!up.ok) return { status: 502, json: { error: `sync failed (HTTP ${up.status})` } };
24754
+ return { status: 200, json: { ok: true, machine: machine.label, ...combined } };
24755
+ } catch (e) {
24756
+ return { status: 502, json: { error: e.message.slice(0, 200) } };
24757
+ }
24758
+ }
24468
24759
 
24469
24760
  // src/server.ts
24470
24761
  var __dirname = path33.dirname(fileURLToPath2(import.meta.url));
@@ -24792,6 +25083,11 @@ function startServer(opts) {
24792
25083
  json(res, out.status, out.json);
24793
25084
  return;
24794
25085
  }
25086
+ if (req.method === "POST" && route === "/api/analysis/sync") {
25087
+ const out = await handleAnalysisSync(dataDir);
25088
+ json(res, out.status, out.json);
25089
+ return;
25090
+ }
24795
25091
  if (req.method === "POST" && route === "/api/analysis/share") {
24796
25092
  const body = JSON.parse(await readBody(req) || "{}");
24797
25093
  const out = await handleAnalysisShare(dataDir, body.kind === "chat" ? "chat" : "coding", {
@@ -24855,7 +25151,7 @@ function startServer(opts) {
24855
25151
 
24856
25152
  // src/pipeline.ts
24857
25153
  import { spawn as spawn7 } from "child_process";
24858
- import os8 from "os";
25154
+ import os9 from "os";
24859
25155
  import path36 from "path";
24860
25156
  import { existsSync as existsSync5, promises as fs31 } from "fs";
24861
25157
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -24867,10 +25163,10 @@ import { promises as fs30 } from "fs";
24867
25163
  import path35 from "path";
24868
25164
 
24869
25165
  // ../../lib/agents/shared/paths.ts
24870
- import os7 from "os";
25166
+ import os8 from "os";
24871
25167
  import path34 from "path";
24872
25168
  function polymathHome() {
24873
- return process.env.POLYMATH_HOME || path34.join(os7.homedir(), ".polymath-society");
25169
+ return process.env.POLYMATH_HOME || path34.join(os8.homedir(), ".polymath-society");
24874
25170
  }
24875
25171
  function agentDir(agent) {
24876
25172
  return path34.join(polymathHome(), agent);
@@ -25029,7 +25325,7 @@ async function startRunLog(clientInfo = {}, opts = {}) {
25029
25325
 
25030
25326
  // src/pipeline.ts
25031
25327
  var MODULE_DIR2 = path36.dirname(fileURLToPath3(import.meta.url));
25032
- function machineBudget(totalMemBytes = os8.totalmem(), cpus = os8.availableParallelism?.() ?? os8.cpus().length, envOverride = process.env.POLYMATH_CONC) {
25328
+ function machineBudget(totalMemBytes = os9.totalmem(), cpus = os9.availableParallelism?.() ?? os9.cpus().length, envOverride = process.env.POLYMATH_CONC) {
25033
25329
  const envB = Number(envOverride);
25034
25330
  if (Number.isFinite(envB) && envB >= 1) return Math.min(32, Math.floor(envB));
25035
25331
  const byMem = Math.floor(totalMemBytes / 2 ** 30 * 0.35 / 0.45);
@@ -25050,6 +25346,7 @@ function stages(o) {
25050
25346
  const conc = String(o.conc ?? h.grade);
25051
25347
  const buildArgs = [];
25052
25348
  if (o.codex === false) buildArgs.push("--no-codex");
25349
+ if (o.claudeCode === false) buildArgs.push("--no-claude");
25053
25350
  if (o.idleGapMin != null) buildArgs.push("--idle", String(o.idleGapMin));
25054
25351
  const gradeArgs = ["--all", "--model", gradeModel, "--conc", conc];
25055
25352
  if (o.regrade) gradeArgs.push("--regrade");
@@ -25279,6 +25576,7 @@ async function runPipeline(o) {
25279
25576
  `\u2713 report is up to date \u2014 ${gate2.pending} new session(s) since the last full run (below the ${threshold}-session threshold). Skipping the AI stages; free metrics still refresh. They'll be analyzed once ${threshold}+ accumulate (POLYMATH_MIN_NEW_SESSIONS=0 or --regrade to force now).`
25280
25577
  );
25281
25578
  } else if (gate2.hasPriorRun) {
25579
+ o.onNewWork?.(gate2.pending);
25282
25580
  o.onLine?.(
25283
25581
  `\u2713 your finished report from the last run is safe and already serving at the link below \u2014 nothing restarts from scratch. ${gate2.pending >= 0 ? `${gate2.pending} pending session(s)` : "New work"} plus any stages added by an update run on top of it, and sections refresh in place as they finish.`
25284
25582
  );
@@ -25480,6 +25778,8 @@ async function runChatPipeline(o) {
25480
25778
  upToDate = true;
25481
25779
  o.onUpToDate?.();
25482
25780
  o.onLine?.(`\u2713 chat report is up to date \u2014 ${newDocs} new doc(s) since the last full run (below the ${minNewDocs}-doc threshold), so the AI stages are skipped and the existing report is served as-is. POLYMATH_MIN_NEW_DOCS=0 forces a full run.`);
25781
+ } else {
25782
+ o.onNewWork?.(newDocs);
25483
25783
  }
25484
25784
  }
25485
25785
  }
@@ -25719,6 +26019,11 @@ Graded ${detail.gradedSessions} sessions \u2192 ${gradeOut}/graded.json`);
25719
26019
  }
25720
26020
  if (opts.cmd === "serve") {
25721
26021
  let earlyHandle = null;
26022
+ const consentManifest = await readManifest(DATA_ROOT);
26023
+ const consent = agentFlagsFromManifest(consentManifest);
26024
+ const consentWhy = (id) => consentManifest?.agents?.excluded.includes(id) ? "you excluded it during setup" : "setup never asked about it";
26025
+ if (!consent.claudeCode) console.error(` \u26D4 Claude Code logs will NOT be analyzed \u2014 ${consentWhy("claude-code")}. POLYMATH_WIZARD=1 polymath-society to change.`);
26026
+ if (!consent.codex) console.error(` \u26D4 Codex logs will NOT be analyzed \u2014 ${consentWhy("codex")}. POLYMATH_WIZARD=1 polymath-society to change.`);
25722
26027
  const runMarker = path41.join(DATA_ROOT, "coding", ".grade-run.json");
25723
26028
  if (!opts.grade) {
25724
26029
  try {
@@ -25796,12 +26101,19 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
25796
26101
  mbar.setLink(earlyHandle.url);
25797
26102
  const codingLane = mbar.lane("coding");
25798
26103
  let codingPinned = false;
26104
+ if (await readPriorAnalysis()) {
26105
+ codingPinned = true;
26106
+ progress.coding = { i: 2, total: 1, label: "up to date \u2014 checking for anything new", done: false };
26107
+ earlyHandle.setProgress(progress);
26108
+ codingLane.finish("up to date \u2014 checking for anything new");
26109
+ }
25799
26110
  const codingDone = runPipeline({
25800
26111
  repoRoot: REPO_ROOT,
25801
26112
  model: opts.model,
25802
26113
  conc: opts.conc,
25803
26114
  overnight: opts.overnight,
25804
- codex: opts.codex,
26115
+ codex: opts.codex && consent.codex,
26116
+ claudeCode: consent.claudeCode,
25805
26117
  idleGapMin: opts.idleGapMin,
25806
26118
  regrade: opts.regrade,
25807
26119
  onUpToDate: () => {
@@ -25810,14 +26122,22 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
25810
26122
  earlyHandle.setProgress(progress);
25811
26123
  codingLane.finish("everything up to date");
25812
26124
  },
26125
+ onNewWork: (pending2) => {
26126
+ codingPinned = false;
26127
+ codingLane.log(` ${pending2} new session(s) to analyze \u2014 updating your report in place.`);
26128
+ },
25813
26129
  onStage: (i, total, label, llm) => {
25814
26130
  if (codingPinned) return;
25815
26131
  progress.coding = { ...progress.coding, i, total, label, done: false };
25816
26132
  earlyHandle.setProgress(progress);
25817
26133
  codingLane.stage(i, total, `${label}${llm ? " (AI)" : ""}`);
25818
26134
  },
26135
+ // While pinned, the scrolling stage output stays quiet ("prints hella
26136
+ // things when everything is cached", owner 2026-07-17) — warnings and
26137
+ // failures always pass through.
25819
26138
  onLine: (line) => {
25820
26139
  harvestWithin("coding", line);
26140
+ if (codingPinned && !/⚠|error|fail/i.test(line)) return;
25821
26141
  codingLane.log(` ${line}`);
25822
26142
  }
25823
26143
  }).then(async (codingRes2) => {
@@ -25850,6 +26170,16 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
25850
26170
  }
25851
26171
  chatLane.log(` chat analysis starting over your ${manifest.exports.included.length} approved source(s) \u2014 same engine as the hosted app.`);
25852
26172
  let chatPinned = false;
26173
+ const chatComplete = await fs36.readFile(path41.join(DATA_ROOT, "engine-pilot", "public-report.json"), "utf8").then((s) => {
26174
+ const r = JSON.parse(s);
26175
+ return !!(r.headline && (r.spikes?.length ?? 0) > 0);
26176
+ }).catch(() => false);
26177
+ if (chatComplete) {
26178
+ chatPinned = true;
26179
+ progress.chat = { i: 2, total: 1, label: "up to date \u2014 checking for anything new", done: false };
26180
+ earlyHandle.setProgress(progress);
26181
+ chatLane.finish("up to date \u2014 checking for anything new");
26182
+ }
25853
26183
  const chat2 = await runChatPipeline({
25854
26184
  repoRoot: REPO_ROOT,
25855
26185
  model: opts.model,
@@ -25860,6 +26190,10 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
25860
26190
  earlyHandle.setProgress(progress);
25861
26191
  chatLane.finish("everything up to date");
25862
26192
  },
26193
+ onNewWork: (pending2) => {
26194
+ chatPinned = false;
26195
+ chatLane.log(` ${pending2} new doc(s) to analyze \u2014 updating your chat report in place.`);
26196
+ },
25863
26197
  onStage: (i, total, label, llm) => {
25864
26198
  if (chatPinned) return;
25865
26199
  progress.chat = { ...progress.chat, i, total, label, done: false };
@@ -25868,6 +26202,7 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
25868
26202
  },
25869
26203
  onLine: (line) => {
25870
26204
  harvestWithin("chat", line);
26205
+ if (chatPinned && !/⚠|error|fail/i.test(line)) return;
25871
26206
  chatLane.log(` ${line}`);
25872
26207
  }
25873
26208
  });
@@ -25923,7 +26258,8 @@ No analysis here yet \u2014 computing the instant deterministic metrics now (fre
25923
26258
  const det = await runPipeline({
25924
26259
  repoRoot: REPO_ROOT,
25925
26260
  deterministicOnly: true,
25926
- codex: opts.codex,
26261
+ codex: opts.codex && consent.codex,
26262
+ claudeCode: consent.claudeCode,
25927
26263
  idleGapMin: opts.idleGapMin,
25928
26264
  onStage: (i, total, label) => dbar.stage(i, total, label),
25929
26265
  onLine: (line) => dbar.log(` ${line}`)