polymath-society 0.2.19 → 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
@@ -110,6 +110,103 @@ var init_injected = __esm({
110
110
  }
111
111
  });
112
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
+
113
210
  // ../coding-core/dist/raw.js
114
211
  import { promises as fs } from "fs";
115
212
  import path2 from "path";
@@ -382,7 +479,7 @@ function parseCodex(raw, file2) {
382
479
  const role = p.role;
383
480
  const text2 = codexText(p.content).replace(SYSTEM_REMINDER_RE, "").trim();
384
481
  if (role === "user") {
385
- if (text2 && !CODEX_INJECTED_RE.test(text2)) {
482
+ if (text2 && !isCodexInjectedUserText(text2)) {
386
483
  s.humanTurns++;
387
484
  s.humanChars += text2.length;
388
485
  if (!s.firstHuman)
@@ -774,11 +871,12 @@ async function readAllSessions(opts = {}) {
774
871
  demoteTemplateFanouts(all);
775
872
  return all;
776
873
  }
777
- 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;
778
875
  var init_raw = __esm({
779
876
  "../coding-core/dist/raw.js"() {
780
877
  "use strict";
781
878
  init_injected();
879
+ init_codex();
782
880
  SOURCE_ROOT_ENV_VARS = [
783
881
  "CLAUDE_PROJECTS_DIR",
784
882
  "CODEX_SESSIONS_DIR",
@@ -807,7 +905,6 @@ var init_raw = __esm({
807
905
  const n = Date.parse(ts);
808
906
  return Number.isFinite(n) ? n : null;
809
907
  };
810
- CODEX_INJECTED_RE = /^\s*<(environment_context|user_instructions)>/;
811
908
  CURSOR_USER_QUERY_RE = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/;
812
909
  NODE_SQLITE = "node:sqlite";
813
910
  FANOUT_PREFIX = 80;
@@ -1486,6 +1583,11 @@ async function readManifest(dataDir) {
1486
1583
  exports: { included: arr(ex.included), excluded: arr(ex.excluded) }
1487
1584
  };
1488
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
+ }
1489
1591
  async function writeManifest(dataDir, m) {
1490
1592
  await fs22.mkdir(dataDir, { recursive: true });
1491
1593
  await fs22.writeFile(manifestPath(dataDir), JSON.stringify(m, null, 2));
@@ -2182,17 +2284,18 @@ var init_plan = __esm({
2182
2284
  var wizard_exports = {};
2183
2285
  __export(wizard_exports, {
2184
2286
  runWizard: () => runWizard,
2185
- sayExportLinks: () => sayExportLinks
2287
+ sayExportLinks: () => sayExportLinks,
2288
+ scanJsonlDir: () => scanJsonlDir
2186
2289
  });
2187
2290
  import { promises as fs35 } from "fs";
2188
2291
  import os11 from "os";
2189
2292
  import path40 from "path";
2190
2293
  import * as readline from "node:readline/promises";
2191
- async function scanJsonlDir(label, dir) {
2294
+ async function scanJsonlDir(label, dir, maxDepth = 1) {
2192
2295
  let files = 0;
2193
2296
  let newest = 0;
2194
2297
  async function walk(d, depth) {
2195
- if (depth > 1) return;
2298
+ if (depth > maxDepth) return;
2196
2299
  let names = [];
2197
2300
  try {
2198
2301
  names = await fs35.readdir(d);
@@ -2317,8 +2420,8 @@ async function runWizard() {
2317
2420
  });
2318
2421
  say(bold(" Step 1 \xB7 Your coding agents"));
2319
2422
  const roots = sourceRoots();
2320
- const scanRoot = (label, dir) => dir ? scanJsonlDir(label, dir) : Promise.resolve({ label, dir: "(disabled \u2014 log roots redirected via env)", files: 0, newest: null });
2321
- 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)]);
2322
2425
  const agentIds = ["claude-code", "codex"];
2323
2426
  const agentScans = [cc, cx];
2324
2427
  const present = agentIds.filter((_, i) => agentScans[i].files > 0);
@@ -2392,8 +2495,11 @@ async function runWizard() {
2392
2495
  for (const f of await scanForExports(path40.join(os11.homedir(), "Downloads"))) add(f);
2393
2496
  for (const f of await scanForObsidianVaults(os11.homedir())) add(f);
2394
2497
  for (const f of [...previous?.exports.included ?? [], ...previous?.exports.excluded ?? []]) add(f);
2498
+ let linksShown = false;
2395
2499
  if (!found.length) {
2396
2500
  say(dim(" Nothing found in the usual places."));
2501
+ await sayExportLinks(say, /* @__PURE__ */ new Set());
2502
+ linksShown = true;
2397
2503
  if (await yes(` Search deeper with your own local agent? ${dim("(capped at ~90s)")}`, false)) {
2398
2504
  try {
2399
2505
  const { invokeAgent: invokeAgent3 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
@@ -2418,7 +2524,7 @@ async function runWizard() {
2418
2524
  const defaults = found.map(() => true);
2419
2525
  say(` Found ${dim("(identified locally from the files themselves)")}:`);
2420
2526
  found.forEach((f, i) => say(describeExport(f, i, defaults[i], hadPrevious)));
2421
- await sayExportLinks(say, new Set(found.map((f) => f.kind)));
2527
+ if (!linksShown) await sayExportLinks(say, new Set(found.map((f) => f.kind)));
2422
2528
  const include = await pick2(defaults, hadPrevious);
2423
2529
  const manifest = {
2424
2530
  approvedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2438,7 +2544,7 @@ async function runWizard() {
2438
2544
  }
2439
2545
  } else {
2440
2546
  await writeManifest(dataDir, { approvedAt: (/* @__PURE__ */ new Date()).toISOString(), agents, exports: { included: [], excluded: [] } });
2441
- await sayExportLinks(say, /* @__PURE__ */ new Set());
2547
+ if (!linksShown) await sayExportLinks(say, /* @__PURE__ */ new Set());
2442
2548
  }
2443
2549
  {
2444
2550
  const { EXPORT_LINKS: EXPORT_LINKS2 } = await Promise.resolve().then(() => (init_exportLinks(), exportLinks_exports));
@@ -2953,6 +3059,7 @@ init_gate2();
2953
3059
 
2954
3060
  // ../coding-core/dist/messages.js
2955
3061
  init_injected();
3062
+ init_codex();
2956
3063
  import { promises as fs2 } from "fs";
2957
3064
  function extractText2(content) {
2958
3065
  if (typeof content === "string")
@@ -2964,6 +3071,22 @@ function extractText2(content) {
2964
3071
  function isToolResultOnly(content) {
2965
3072
  return Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
2966
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
+ }
2967
3090
  async function extractUserMessages(file2) {
2968
3091
  let raw;
2969
3092
  try {
@@ -2971,6 +3094,8 @@ async function extractUserMessages(file2) {
2971
3094
  } catch {
2972
3095
  return [];
2973
3096
  }
3097
+ if (sniffCodexRaw(raw))
3098
+ return extractUserMessagesCodex(raw);
2974
3099
  const out = [];
2975
3100
  const userTexts = /* @__PURE__ */ new Set();
2976
3101
  const seenUuids = /* @__PURE__ */ new Set();
@@ -3025,6 +3150,49 @@ function createMessageDeduper() {
3025
3150
  return true;
3026
3151
  };
3027
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
+ }
3028
3196
  async function extractMessages(file2) {
3029
3197
  let raw;
3030
3198
  try {
@@ -3032,6 +3200,8 @@ async function extractMessages(file2) {
3032
3200
  } catch {
3033
3201
  return [];
3034
3202
  }
3203
+ if (sniffCodexRaw(raw))
3204
+ return extractMessagesCodex(raw);
3035
3205
  const out = [];
3036
3206
  const userTexts = /* @__PURE__ */ new Set();
3037
3207
  const seenUuids = /* @__PURE__ */ new Set();
@@ -3767,7 +3937,7 @@ var LABEL_BY_UIKEY = Object.fromEntries(GRADED_CRITERIA2.map((c) => [c.uiKey, c.
3767
3937
  // src/grade/materialize.ts
3768
3938
  init_injected();
3769
3939
  import { promises as fs3 } from "fs";
3770
- var SYSTEM_REMINDER_RE2 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
3940
+ var SYSTEM_REMINDER_RE3 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
3771
3941
  var INJECT_GIST = 120;
3772
3942
  var INJECTED = [
3773
3943
  { re: /<task-notification>[\s\S]*?<\/task-notification>/g, label: "bg task", gistRe: /<summary>([\s\S]*?)<\/summary>/ },
@@ -3787,7 +3957,7 @@ function collapseInjected(input) {
3787
3957
  return "";
3788
3958
  });
3789
3959
  }
3790
- return { text: text2.replace(SYSTEM_REMINDER_RE2, "").trim(), markers };
3960
+ return { text: text2.replace(SYSTEM_REMINDER_RE3, "").trim(), markers };
3791
3961
  }
3792
3962
  function extractText3(content) {
3793
3963
  if (typeof content === "string") return content;
@@ -21259,8 +21429,9 @@ import path17 from "node:path";
21259
21429
 
21260
21430
  // ../../lib/agents/coding/materialize.ts
21261
21431
  init_injected();
21432
+ init_codex();
21262
21433
  import { promises as fs11 } from "fs";
21263
- var SYSTEM_REMINDER_RE3 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
21434
+ var SYSTEM_REMINDER_RE4 = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
21264
21435
  var INJECT_GIST2 = 120;
21265
21436
  var INJECTED2 = [
21266
21437
  { re: /<task-notification>[\s\S]*?<\/task-notification>/g, label: "bg task", gistRe: /<summary>([\s\S]*?)<\/summary>/ },
@@ -21280,7 +21451,7 @@ function collapseInjected2(input) {
21280
21451
  return "";
21281
21452
  });
21282
21453
  }
21283
- return { text: text2.replace(SYSTEM_REMINDER_RE3, "").trim(), markers };
21454
+ return { text: text2.replace(SYSTEM_REMINDER_RE4, "").trim(), markers };
21284
21455
  }
21285
21456
  function extractText4(content) {
21286
21457
  if (typeof content === "string") return content;
@@ -21297,6 +21468,38 @@ function isToolResultOnly3(content) {
21297
21468
  return Array.isArray(content) && content.length > 0 && content.every((c) => c && typeof c === "object" && c.type === "tool_result");
21298
21469
  }
21299
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
+ }
21300
21503
  async function materializeSession2(file2) {
21301
21504
  let raw;
21302
21505
  try {
@@ -21304,6 +21507,7 @@ async function materializeSession2(file2) {
21304
21507
  } catch {
21305
21508
  return null;
21306
21509
  }
21510
+ if (sniffCodexRaw(raw)) return materializeCodex(raw);
21307
21511
  const lines = [];
21308
21512
  let humanTurns = 0, humanChars = 0;
21309
21513
  const pendingQueued = [];
@@ -21839,124 +22043,6 @@ function handleDiagnostic(method, body, key2) {
21839
22043
  // src/codingChat.ts
21840
22044
  init_agent();
21841
22045
 
21842
- // ../../lib/agents/coding/chat-context.ts
21843
- var RUNGS_BY_KEY = new Map(CODING_CRITERIA.map((c) => [c.key, c.rungs ?? []]));
21844
- var clip = (s, n) => {
21845
- const t = (s || "").replace(/\s+/g, " ").trim();
21846
- return t.length > n ? t.slice(0, n - 1).replace(/\s+\S*$/, "") + "\u2026" : t;
21847
- };
21848
- function criterionBlock(c) {
21849
- if (!c.graded || c.dist.n <= 0) return null;
21850
- const lines = [];
21851
- const mean2 = c.dist.mean != null ? `${c.dist.mean}` : "\u2014";
21852
- lines.push(`### ${c.label} (your mean ${mean2} across ${c.dist.n} graded session${c.dist.n === 1 ? "" : "s"})`);
21853
- lines.push(`What it measures: ${clip(c.definition, 400)}`);
21854
- const rungs = RUNGS_BY_KEY.get(c.key) ?? [];
21855
- if (rungs.length) {
21856
- lines.push(`The ladder I grade against:`);
21857
- for (const r of rungs) lines.push(` L${r.level} \u2014 ${r.marker}`);
21858
- }
21859
- const takes = (c.takes || []).slice(0, 5);
21860
- if (takes.length) {
21861
- lines.push(`How I scored your sessions here:`);
21862
- for (const t of takes) {
21863
- const sc = t.score != null ? `${t.score}` : t.best != null ? `~${t.best}` : "\u2014";
21864
- const parts = [` \u2022 "${clip(t.title, 80)}" (${(t.date || "").slice(0, 10)}) \u2014 scored ${sc}${t.confidence ? ` (${t.confidence})` : ""}.`];
21865
- if (t.instance) parts.push(`What showed it: ${clip(t.instance, 240)}`);
21866
- if (t.why) parts.push(`Why that score: ${clip(t.why, 320)}`);
21867
- const qs = (t.quotes || []).filter(Boolean).slice(0, 2).map((q) => `"${clip(q, 160)}"`);
21868
- if (qs.length) parts.push(`You said: ${qs.join(" / ")}`);
21869
- lines.push(parts.join(" "));
21870
- }
21871
- }
21872
- return lines.join("\n");
21873
- }
21874
- function buildCodingContext(profile) {
21875
- const out = [];
21876
- const t = profile.throughput;
21877
- const p = profile.parallelism;
21878
- const meta = [];
21879
- if (t?.sessions != null) meta.push(`${t.sessions} interactive coding sessions`);
21880
- if (profile.gradedSessions != null) meta.push(`${profile.gradedSessions} graded`);
21881
- if (p?.maxConcurrency != null) meta.push(`peak ${p.maxConcurrency} sessions in parallel`);
21882
- if (meta.length) out.push(`HOW THEY WORK: ${meta.join(" \xB7 ")}.`);
21883
- const blocks = (profile.criteria || []).map(criterionBlock).filter((b) => !!b);
21884
- if (blocks.length) {
21885
- out.push(`
21886
- YOUR GRADED CRITERIA \u2014 the rubric ladder and how I scored you on each, with your own words as the receipts:`);
21887
- out.push(blocks.join("\n\n"));
21888
- } else {
21889
- out.push(`
21890
- (No criteria have been graded yet, so I can only speak to how you work, not the qualitative scores.)`);
21891
- }
21892
- return out.join("\n");
21893
- }
21894
- 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.
21895
-
21896
- HOW TO ANSWER:
21897
- - 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.
21898
- - 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.
21899
- - 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.
21900
- - 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.
21901
- - Keep replies tight and human unless they ask you to go deep. Output ONLY your reply \u2014 no preamble, no meta.
21902
-
21903
- Do NOT use em dashes. Use a comma or a period instead.`;
21904
- function stripSlop(raw) {
21905
- return (raw || "").trim().replace(/\s*—\s*/g, ", ").replace(/\s+–\s+/g, ", ").replace(/\s+,/g, ",");
21906
- }
21907
- function buildCodingChatPrompt(profile, messages) {
21908
- const context = buildCodingContext(profile);
21909
- const history = messages.map((m) => `${m.role === "user" ? "Them" : "You"}: ${m.content}`).join("\n\n");
21910
- return `YOUR READ OF THIS PERSON (the rubric + your grades + their receipts):
21911
- ${context}
21912
-
21913
- --- the conversation so far ---
21914
- ${history}
21915
-
21916
- Write your next reply to them now.`;
21917
- }
21918
-
21919
- // src/codingChat.ts
21920
- async function handleCodingChat(profile, dataDir, body, res) {
21921
- const raw = Array.isArray(body.messages) ? body.messages : [];
21922
- const messages = raw.filter((m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string").slice(-20);
21923
- if (!messages.length) {
21924
- res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
21925
- res.end(JSON.stringify({ error: "no messages" }));
21926
- return;
21927
- }
21928
- const prompt = buildCodingChatPrompt(profile, messages);
21929
- res.writeHead(200, {
21930
- "Content-Type": "application/x-ndjson; charset=utf-8",
21931
- "Cache-Control": "no-store, no-transform",
21932
- "X-Accel-Buffering": "no"
21933
- });
21934
- const send = (o) => {
21935
- try {
21936
- res.write(JSON.stringify(o) + "\n");
21937
- } catch {
21938
- }
21939
- };
21940
- try {
21941
- const run4 = await invokeAgent({
21942
- prompt,
21943
- systemPrompt: CODING_CHAT_SYSTEM,
21944
- // Everything it needs is in the prompt; dataDir is a harmless read-only
21945
- // sandbox. maxTurns 2 → answer directly, never a tool-loop.
21946
- addDir: dataDir,
21947
- allowedTools: ["Read"],
21948
- maxTurns: 2,
21949
- model: "sonnet",
21950
- onText: (delta) => send({ t: "delta", v: delta })
21951
- });
21952
- send({ t: "done", reply: stripSlop(run4.raw || "") || "(no response)" });
21953
- } catch {
21954
- send({ t: "error", message: "Couldn't reach your local Claude \u2014 is Claude Code (or Codex) installed and logged in?" });
21955
- } finally {
21956
- res.end();
21957
- }
21958
- }
21959
-
21960
22046
  // src/personApi.ts
21961
22047
  init_agent();
21962
22048
  import { promises as fs18 } from "fs";
@@ -22519,6 +22605,137 @@ ${dataMap}`,
22519
22605
  }
22520
22606
  }
22521
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
+
22522
22739
  // src/publicReportApi.ts
22523
22740
  init_agent();
22524
22741
 
@@ -23555,6 +23772,18 @@ function distOf3(takes) {
23555
23772
  const hist = [...h.keys()].sort((a, b) => b - a).map((level) => ({ level, count: h.get(level) }));
23556
23773
  return { n, observed, mean: mean2, median: median2, hist };
23557
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
+ }
23558
23787
  async function compileCodingProfile() {
23559
23788
  const allRecords = await readJson2(path30.join(CODING_DIR2, "sessions.json"), []);
23560
23789
  const dupIds = new Set(allRecords.filter((s) => s.duplicateOf).map((s) => s.sessionId));
@@ -23809,15 +24038,11 @@ async function compileCodingProfile() {
23809
24038
  const unb = ceilOf("unblocking");
23810
24039
  const agency = out2 != null && unb != null ? Math.round((out2 + unb) / 2) : out2 ?? unb;
23811
24040
  const sRow = (label, score, gap2) => ({ label, score, percentile: pctOf(score), gap: gap2 });
23812
- const tiers = DEFAULT_CALIBRATION.codingTiers;
23813
- const phi = (z) => {
23814
- const t = 1 / (1 + 0.2316419 * Math.abs(z));
23815
- const d = 0.3989423 * Math.exp(-z * z / 2);
23816
- let pr = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))));
23817
- if (z > 0) pr = 1 - pr;
23818
- 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);
23819
24044
  };
23820
- 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;
23821
24046
  const avgWords = aggregate?.throughput?.avgWordsPerDay ?? null;
23822
24047
  const pushPctl = lognPct(avgWords, tiers?.push);
23823
24048
  const focusPctl = lognPct(flowPctDay, tiers?.focus);
@@ -23836,10 +24061,16 @@ async function compileCodingProfile() {
23836
24061
  null,
23837
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"
23838
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.
23839
24070
  sRow(
23840
24071
  "Your AI parallelism",
23841
24072
  ceilOf("delegation"),
23842
- 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"
23843
24074
  ),
23844
24075
  // The evidence line cites the RANKING metric (flow as a share of the work
23845
24076
  // day), never absolute hours — a friend with MORE flow hours over a longer
@@ -23855,22 +24086,25 @@ async function compileCodingProfile() {
23855
24086
  // The four estimates LEAD with a concrete moment from their own sessions, then an
23856
24087
  // HONEST caveat — how many graded sessions back the read. No invented weakness, no
23857
24088
  // claim about what they "didn't" do (false specifics churn a real user); it's an estimate.
23858
- sRow(
24089
+ est(
23859
24090
  "Agency",
23860
24091
  agency,
24092
+ "outcomes",
23861
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`
23862
24094
  ),
23863
24095
  // Judgement/Taste use the SAME numbers their ability cards prove (aggregate
23864
24096
  // peak-with-consistency over near-peak instances) — a chip saying "top 1%"
23865
24097
  // above a card proving 10/Top 0.1% with ten receipts reads as a bug.
23866
- sRow(
24098
+ est(
23867
24099
  "Judgement",
23868
24100
  aggregate?.ability?.abstraction?.score ?? ceilOf("generative"),
24101
+ "generative",
23869
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`
23870
24103
  ),
23871
- sRow(
24104
+ est(
23872
24105
  "Taste",
23873
24106
  aggregate?.ability?.taste?.score ?? ceilOf("taste"),
24107
+ "taste",
23874
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`
23875
24109
  )
23876
24110
  // Expertise: NO chip — the scored criterion is deprecated; the real judge is
@@ -25112,6 +25346,7 @@ function stages(o) {
25112
25346
  const conc = String(o.conc ?? h.grade);
25113
25347
  const buildArgs = [];
25114
25348
  if (o.codex === false) buildArgs.push("--no-codex");
25349
+ if (o.claudeCode === false) buildArgs.push("--no-claude");
25115
25350
  if (o.idleGapMin != null) buildArgs.push("--idle", String(o.idleGapMin));
25116
25351
  const gradeArgs = ["--all", "--model", gradeModel, "--conc", conc];
25117
25352
  if (o.regrade) gradeArgs.push("--regrade");
@@ -25341,6 +25576,7 @@ async function runPipeline(o) {
25341
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).`
25342
25577
  );
25343
25578
  } else if (gate2.hasPriorRun) {
25579
+ o.onNewWork?.(gate2.pending);
25344
25580
  o.onLine?.(
25345
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.`
25346
25582
  );
@@ -25542,6 +25778,8 @@ async function runChatPipeline(o) {
25542
25778
  upToDate = true;
25543
25779
  o.onUpToDate?.();
25544
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);
25545
25783
  }
25546
25784
  }
25547
25785
  }
@@ -25781,6 +26019,11 @@ Graded ${detail.gradedSessions} sessions \u2192 ${gradeOut}/graded.json`);
25781
26019
  }
25782
26020
  if (opts.cmd === "serve") {
25783
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.`);
25784
26027
  const runMarker = path41.join(DATA_ROOT, "coding", ".grade-run.json");
25785
26028
  if (!opts.grade) {
25786
26029
  try {
@@ -25858,12 +26101,19 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
25858
26101
  mbar.setLink(earlyHandle.url);
25859
26102
  const codingLane = mbar.lane("coding");
25860
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
+ }
25861
26110
  const codingDone = runPipeline({
25862
26111
  repoRoot: REPO_ROOT,
25863
26112
  model: opts.model,
25864
26113
  conc: opts.conc,
25865
26114
  overnight: opts.overnight,
25866
- codex: opts.codex,
26115
+ codex: opts.codex && consent.codex,
26116
+ claudeCode: consent.claudeCode,
25867
26117
  idleGapMin: opts.idleGapMin,
25868
26118
  regrade: opts.regrade,
25869
26119
  onUpToDate: () => {
@@ -25872,14 +26122,22 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
25872
26122
  earlyHandle.setProgress(progress);
25873
26123
  codingLane.finish("everything up to date");
25874
26124
  },
26125
+ onNewWork: (pending2) => {
26126
+ codingPinned = false;
26127
+ codingLane.log(` ${pending2} new session(s) to analyze \u2014 updating your report in place.`);
26128
+ },
25875
26129
  onStage: (i, total, label, llm) => {
25876
26130
  if (codingPinned) return;
25877
26131
  progress.coding = { ...progress.coding, i, total, label, done: false };
25878
26132
  earlyHandle.setProgress(progress);
25879
26133
  codingLane.stage(i, total, `${label}${llm ? " (AI)" : ""}`);
25880
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.
25881
26138
  onLine: (line) => {
25882
26139
  harvestWithin("coding", line);
26140
+ if (codingPinned && !/⚠|error|fail/i.test(line)) return;
25883
26141
  codingLane.log(` ${line}`);
25884
26142
  }
25885
26143
  }).then(async (codingRes2) => {
@@ -25912,6 +26170,16 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
25912
26170
  }
25913
26171
  chatLane.log(` chat analysis starting over your ${manifest.exports.included.length} approved source(s) \u2014 same engine as the hosted app.`);
25914
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
+ }
25915
26183
  const chat2 = await runChatPipeline({
25916
26184
  repoRoot: REPO_ROOT,
25917
26185
  model: opts.model,
@@ -25922,6 +26190,10 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
25922
26190
  earlyHandle.setProgress(progress);
25923
26191
  chatLane.finish("everything up to date");
25924
26192
  },
26193
+ onNewWork: (pending2) => {
26194
+ chatPinned = false;
26195
+ chatLane.log(` ${pending2} new doc(s) to analyze \u2014 updating your chat report in place.`);
26196
+ },
25925
26197
  onStage: (i, total, label, llm) => {
25926
26198
  if (chatPinned) return;
25927
26199
  progress.chat = { ...progress.chat, i, total, label, done: false };
@@ -25930,6 +26202,7 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
25930
26202
  },
25931
26203
  onLine: (line) => {
25932
26204
  harvestWithin("chat", line);
26205
+ if (chatPinned && !/⚠|error|fail/i.test(line)) return;
25933
26206
  chatLane.log(` ${line}`);
25934
26207
  }
25935
26208
  });
@@ -25985,7 +26258,8 @@ No analysis here yet \u2014 computing the instant deterministic metrics now (fre
25985
26258
  const det = await runPipeline({
25986
26259
  repoRoot: REPO_ROOT,
25987
26260
  deterministicOnly: true,
25988
- codex: opts.codex,
26261
+ codex: opts.codex && consent.codex,
26262
+ claudeCode: consent.claudeCode,
25989
26263
  idleGapMin: opts.idleGapMin,
25990
26264
  onStage: (i, total, label) => dbar.stage(i, total, label),
25991
26265
  onLine: (line) => dbar.log(` ${line}`)