polymath-society 0.2.30 → 0.2.31

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.
Files changed (2) hide show
  1. package/dist/cli.js +301 -234
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -22828,15 +22828,167 @@ var init_publicCodingPageApi = __esm({
22828
22828
  }
22829
22829
  });
22830
22830
 
22831
+ // src/plan.ts
22832
+ var plan_exports = {};
22833
+ __export(plan_exports, {
22834
+ UNSUPPORTED_FULL_TIERS: () => UNSUPPORTED_FULL_TIERS,
22835
+ detectCodexPlan: () => detectCodexPlan,
22836
+ detectPlan: () => detectPlan,
22837
+ estimateLaneCosts: () => estimateLaneCosts,
22838
+ opusAvailable: () => opusAvailable,
22839
+ remainingFractions: () => remainingFractions,
22840
+ scheduleLanes: () => scheduleLanes,
22841
+ supportsFullAnalysis: () => supportsFullAnalysis,
22842
+ tierFromChatgptPlan: () => tierFromChatgptPlan,
22843
+ tierFromStrings: () => tierFromStrings
22844
+ });
22845
+ import { promises as fs32 } from "fs";
22846
+ import os8 from "os";
22847
+ import path38 from "path";
22848
+ function supportsFullAnalysis(tier) {
22849
+ if (process.env.POLYMATH_FORCE_FULL === "1") return true;
22850
+ return !UNSUPPORTED_FULL_TIERS.includes(tier);
22851
+ }
22852
+ function opusAvailable(tier) {
22853
+ return tier === "max_20x" || tier === "max_5x";
22854
+ }
22855
+ function tierFromStrings(...hints) {
22856
+ for (const h of hints) {
22857
+ if (!h) continue;
22858
+ const s = h.toLowerCase();
22859
+ if (s.includes("max_20x") || s.includes("max20x")) return "max_20x";
22860
+ if (s.includes("max_5x") || s.includes("max5x")) return "max_5x";
22861
+ if (s.includes("pro")) return "pro";
22862
+ if (s.includes("claude_max")) return "max_5x";
22863
+ }
22864
+ return "unknown";
22865
+ }
22866
+ function tierFromChatgptPlan(planType) {
22867
+ const s = (planType ?? "").toLowerCase();
22868
+ if (s === "pro") return "gpt_pro";
22869
+ if (s === "plus") return "gpt_plus";
22870
+ if (s === "team" || s === "enterprise" || s === "business") return "gpt_team";
22871
+ if (s === "free") return "gpt_free";
22872
+ return "unknown";
22873
+ }
22874
+ async function detectCodexPlan(home = os8.homedir()) {
22875
+ try {
22876
+ const raw = JSON.parse(await fs32.readFile(path38.join(home, ".codex", "auth.json"), "utf8"));
22877
+ for (const tok of [raw.tokens?.id_token, raw.tokens?.access_token]) {
22878
+ if (!tok) continue;
22879
+ try {
22880
+ const payload = JSON.parse(Buffer.from(tok.split(".")[1], "base64url").toString("utf8"));
22881
+ const t = tierFromChatgptPlan(payload["https://api.openai.com/auth"]?.chatgpt_plan_type);
22882
+ if (t !== "unknown") return t;
22883
+ } catch {
22884
+ }
22885
+ }
22886
+ } catch {
22887
+ }
22888
+ return "unknown";
22889
+ }
22890
+ async function detectPlan(home = os8.homedir()) {
22891
+ let tier = "unknown";
22892
+ try {
22893
+ const raw = JSON.parse(await fs32.readFile(path38.join(home, ".claude.json"), "utf8"));
22894
+ const oa = raw.oauthAccount ?? {};
22895
+ tier = tierFromStrings(oa.userRateLimitTier, oa.organizationRateLimitTier, oa.organizationType);
22896
+ } catch {
22897
+ }
22898
+ if (tier === "unknown") tier = await detectCodexPlan(home);
22899
+ return { tier, label: LABEL[tier], windowUsd: WINDOW_USD[tier] };
22900
+ }
22901
+ function estimateLaneCosts(input) {
22902
+ const codingUsd = Math.max(5, Math.min(32, Math.round(input.agentLogFiles * 9e-4)));
22903
+ const chatUsd = input.chatIncluded ? Math.round(40 + input.exportTotalMB * 0.8) : 0;
22904
+ return { codingUsd, chatUsd };
22905
+ }
22906
+ async function remainingFractions(dataRoot, agentLogFiles) {
22907
+ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
22908
+ let coding = 1;
22909
+ try {
22910
+ const graded = (await fs32.readdir(path38.join(dataRoot, "coding", "grades"))).filter((f) => f.endsWith(".json")).length;
22911
+ const expected = Math.max(1, agentLogFiles * 56e-4);
22912
+ coding = clamp(1 - graded / expected, 0.05, 1);
22913
+ } catch {
22914
+ }
22915
+ let chatDone = 0;
22916
+ try {
22917
+ const st = await fs32.stat(path38.join(dataRoot, "signals", "index.jsonl"));
22918
+ if (st.size > 0) chatDone += 0.3;
22919
+ } catch {
22920
+ }
22921
+ try {
22922
+ const lenses = await fs32.readdir(path38.join(dataRoot, "engine-pilot", "grades"));
22923
+ let withPerson = 0;
22924
+ for (const l of lenses) {
22925
+ try {
22926
+ await fs32.stat(path38.join(dataRoot, "engine-pilot", "grades", l, "_person.json"));
22927
+ withPerson++;
22928
+ } catch {
22929
+ }
22930
+ }
22931
+ chatDone += 0.6 * Math.min(1, withPerson / 5);
22932
+ } catch {
22933
+ }
22934
+ return { coding, chat: clamp(1 - chatDone, 0.1, 1) };
22935
+ }
22936
+ function scheduleLanes(plan, est) {
22937
+ const chat = est.chatUsd === 0 || est.chatUsd <= plan.windowUsd * 0.9 ? "now" : "overnight";
22938
+ return {
22939
+ coding: "now",
22940
+ chat,
22941
+ // No plan name here — the schedule header names the plan once, and
22942
+ // repeating it doubled the clumsy unknown-plan label (2026-07-22
22943
+ // walkthrough). Phrased for the two-column "Proposed schedule" rows:
22944
+ // "<lane> <verdict> — <reason>".
22945
+ codingReason: `runs now \u2014 the report link appears right away and fills in as it goes`,
22946
+ chatReason: est.chatUsd === 0 ? "not planned \u2014 no chat sources included" : chat === "overnight" ? `starts at 2:00 am tonight \u2014 a chat corpus this size is an overnight job; it runs while you sleep` : `runs now, alongside coding`
22947
+ };
22948
+ }
22949
+ var UNSUPPORTED_FULL_TIERS, WINDOW_USD, LABEL;
22950
+ var init_plan = __esm({
22951
+ "src/plan.ts"() {
22952
+ "use strict";
22953
+ UNSUPPORTED_FULL_TIERS = ["gpt_plus", "gpt_free"];
22954
+ WINDOW_USD = {
22955
+ max_20x: 300,
22956
+ max_5x: 75,
22957
+ pro: 15,
22958
+ // ChatGPT tiers, mapped to rough Claude-window parity by monthly price
22959
+ // ($200 Pro ≈ Max 20x; $20 Plus ≈ Claude Pro; Team between; free ≈ nothing).
22960
+ gpt_pro: 300,
22961
+ gpt_plus: 20,
22962
+ gpt_team: 50,
22963
+ gpt_free: 5,
22964
+ unknown: 15
22965
+ // conservative: schedule like Pro when we can't tell
22966
+ };
22967
+ LABEL = {
22968
+ max_20x: "Claude Max 20x",
22969
+ max_5x: "Claude Max 5x",
22970
+ pro: "Claude Pro",
22971
+ gpt_pro: "ChatGPT Pro (Codex)",
22972
+ gpt_plus: "ChatGPT Plus (Codex)",
22973
+ gpt_team: "ChatGPT Team (Codex)",
22974
+ gpt_free: "ChatGPT Free (Codex)",
22975
+ unknown: "an undetected plan (scheduling conservatively)"
22976
+ };
22977
+ }
22978
+ });
22979
+
22831
22980
  // src/pipeline.ts
22832
22981
  var pipeline_exports = {};
22833
22982
  __export(pipeline_exports, {
22983
+ TAIL_MODEL_ENV: () => TAIL_MODEL_ENV,
22984
+ applyTailModelEnv: () => applyTailModelEnv,
22834
22985
  apportionHeavy: () => apportionHeavy,
22835
22986
  codingDataRoot: () => codingDataRoot,
22836
22987
  evalUpToDate: () => evalUpToDate,
22837
22988
  expectedLlmArtifacts: () => expectedLlmArtifacts,
22838
22989
  hasPipeline: () => hasPipeline,
22839
22990
  machineBudget: () => machineBudget,
22991
+ resolveTailModel: () => resolveTailModel,
22840
22992
  runPipeline: () => runPipeline,
22841
22993
  stages: () => stages,
22842
22994
  startKeepAwake: () => startKeepAwake,
@@ -22845,11 +22997,11 @@ __export(pipeline_exports, {
22845
22997
  upToDateSkip: () => upToDateSkip
22846
22998
  });
22847
22999
  import { spawn as spawn7 } from "child_process";
22848
- import os8 from "os";
22849
- import path38 from "path";
22850
- import { existsSync as existsSync5, promises as fs32 } from "fs";
23000
+ import os9 from "os";
23001
+ import path39 from "path";
23002
+ import { existsSync as existsSync5, promises as fs33 } from "fs";
22851
23003
  import { fileURLToPath as fileURLToPath3 } from "url";
22852
- function machineBudget(totalMemBytes = os8.totalmem(), cpus = os8.availableParallelism?.() ?? os8.cpus().length, envOverride = process.env.POLYMATH_CONC) {
23004
+ function machineBudget(totalMemBytes = os9.totalmem(), cpus = os9.availableParallelism?.() ?? os9.cpus().length, envOverride = process.env.POLYMATH_CONC) {
22853
23005
  const envB = Number(envOverride);
22854
23006
  if (Number.isFinite(envB) && envB >= 1) return Math.min(32, Math.floor(envB));
22855
23007
  const byMem = Math.floor(totalMemBytes / 2 ** 30 * 0.35 / 0.45);
@@ -22876,6 +23028,8 @@ function stages(o) {
22876
23028
  if (o.idleGapMin != null) buildArgs.push("--idle", String(o.idleGapMin));
22877
23029
  const gradeArgs = ["--all", "--model", gradeModel, "--conc", conc];
22878
23030
  if (o.regrade) gradeArgs.push("--regrade");
23031
+ const tailModel = o.model ?? o.tailModel;
23032
+ const tailArgs = tailModel ? ["--model", tailModel] : [];
22879
23033
  return [
22880
23034
  // ---- deterministic foundation (fast, free) ----
22881
23035
  { script: "coding-build.mts", label: "Scrape + classify your sessions", llm: false, args: buildArgs, out: "coding/sessions.json" },
@@ -22907,10 +23061,10 @@ function stages(o) {
22907
23061
  // ---- corpus aggregate (reads grades; deterministic rollup) ----
22908
23062
  { script: "coding-aggregate.mts", label: "Corpus aggregate \u2014 ability ceiling + flow", llm: false, args: [], out: "coding/aggregate.json" },
22909
23063
  // ---- grade readers (grades/* is complete once aggregate ran) ----
22910
- // No default --model here: the agglomerate script defaults itself to Opus
22911
- // (one call per run; the axis verdicts are the most judgment-heavy output).
22912
- // An explicit user model choice still wins.
22913
- { script: "coding-agglomerate.mts", label: "Agglomerated criteria + verdicts + signature moves", llm: true, weight: 4, args: o.model ? ["--model", o.model] : [], out: "coding/agglomerate.json", batch: "grade-readers" },
23064
+ // The agglomerate script defaults itself to Opus (one call per run; the
23065
+ // axis verdicts are the most judgment-heavy output). tailArgs overrides
23066
+ // that on plans that can't run Opus; an explicit user model choice wins.
23067
+ { script: "coding-agglomerate.mts", label: "Agglomerated criteria + verdicts + signature moves", llm: true, weight: 4, args: [...tailArgs], out: "coding/agglomerate.json", batch: "grade-readers" },
22914
23068
  // Domain-expertise map DISABLED (owner call 2026-07-22): the single most
22915
23069
  // expensive stage of the whole run ($13.88 of $49.34 measured — 129 sonnet
22916
23070
  // calls) and for most users the hiring-bar filter correctly returns ZERO
@@ -22924,34 +23078,44 @@ function stages(o) {
22924
23078
  // Work brief — the hiring-manager narrative + continuous timeline. Consumes
22925
23079
  // day-digest.json (heavy batch, already complete) + concurrency/projects;
22926
23080
  // disjoint output, so it rides the synthesis batch. Defaults itself to Opus
22927
- // (the journey's significance calls are judgment-heavy); an explicit model
22928
- // choice wins. Prompt-hash gated + up-to-date-skip like its siblings.
22929
- { script: "coding-workbrief.mts", label: "Work brief \u2014 narrative timeline", llm: true, weight: 3, args: o.model ? ["--model", o.model] : [], out: "coding/workbrief.json", batch: "synthesis" },
23081
+ // (the journey's significance calls are judgment-heavy); tailArgs / an
23082
+ // explicit model choice win. Prompt-hash gated + up-to-date-skip like its siblings.
23083
+ { script: "coding-workbrief.mts", label: "Work brief \u2014 narrative timeline", llm: true, weight: 3, args: [...tailArgs], out: "coding/workbrief.json", batch: "synthesis" },
22930
23084
  // Growth reads grades/* + deterministic artifacts only (no per-session AI
22931
23085
  // calls — it deliberately ignores the AI recency window and covers the full
22932
23086
  // history); disjoint output, so it rides the synthesis batch. Like
22933
- // agglomerate it defaults itself to Opus; an explicit model choice wins.
22934
- { script: "coding-growth.mts", label: "Growth arcs + learning rate", llm: true, weight: 3, args: o.model ? ["--model", o.model] : [], out: "coding/growth.json", batch: "synthesis" },
23087
+ // agglomerate it defaults itself to Opus; tailArgs / an explicit model choice win.
23088
+ { script: "coding-growth.mts", label: "Growth arcs + learning rate", llm: true, weight: 3, args: [...tailArgs], out: "coding/growth.json", batch: "synthesis" },
22935
23089
  // nutshell stays LAST + alone: it merges INTO aggregate.json while the two
22936
23090
  // above read the compiled profile — racing them re-reads mid-write.
22937
23091
  { script: "coding-nutshell.mts", label: "Big-picture nutshell", llm: true, weight: 1, args: [], out: "coding/aggregate.json" }
22938
23092
  // merges bigPicture INTO aggregate.json
22939
23093
  ];
22940
23094
  }
23095
+ function applyTailModelEnv(env, tailModel) {
23096
+ if (!tailModel) return;
23097
+ for (const key2 of Object.values(TAIL_MODEL_ENV)) if (!env[key2]) env[key2] = tailModel;
23098
+ }
23099
+ async function resolveTailModel(o, home) {
23100
+ if (o.model || o.tailModel) return o.tailModel;
23101
+ const { detectPlan: detectPlan2, opusAvailable: opusAvailable2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
23102
+ const { tier } = await detectPlan2(home);
23103
+ return opusAvailable2(tier) ? void 0 : "sonnet";
23104
+ }
22941
23105
  function bundledPipelineDir() {
22942
- const d = path38.join(MODULE_DIR2, "pipeline");
22943
- return existsSync5(path38.join(d, "coding-build.js")) ? d : null;
23106
+ const d = path39.join(MODULE_DIR2, "pipeline");
23107
+ return existsSync5(path39.join(d, "coding-build.js")) ? d : null;
22944
23108
  }
22945
23109
  function stageInvocation(repoRoot, scriptRel, args) {
22946
- const scriptAbs = path38.join(repoRoot, "scripts", scriptRel);
23110
+ const scriptAbs = path39.join(repoRoot, "scripts", scriptRel);
22947
23111
  if (existsSync5(scriptAbs)) {
22948
- const local = path38.join(repoRoot, "node_modules", ".bin", "tsx");
23112
+ const local = path39.join(repoRoot, "node_modules", ".bin", "tsx");
22949
23113
  if (existsSync5(local)) return { cmd: local, argv: [scriptAbs, ...args], cwd: repoRoot };
22950
23114
  return { cmd: "npx", argv: ["tsx", scriptAbs, ...args], cwd: repoRoot };
22951
23115
  }
22952
23116
  const bundled = bundledPipelineDir();
22953
23117
  if (bundled) {
22954
- const js = path38.join(bundled, scriptRel.replace(/\.mts$/, ".js"));
23118
+ const js = path39.join(bundled, scriptRel.replace(/\.mts$/, ".js"));
22955
23119
  return { cmd: process.execPath, argv: [js, ...args], cwd: process.cwd() };
22956
23120
  }
22957
23121
  throw new Error(`pipeline stage ${scriptRel}: neither repo scripts/ nor bundled dist/pipeline/ found`);
@@ -23007,17 +23171,17 @@ function runStage(repoRoot, st, env, onLine) {
23007
23171
  });
23008
23172
  }
23009
23173
  function hasPipeline(repoRoot) {
23010
- return existsSync5(path38.join(repoRoot, "scripts", "coding-build.mts")) || bundledPipelineDir() != null;
23174
+ return existsSync5(path39.join(repoRoot, "scripts", "coding-build.mts")) || bundledPipelineDir() != null;
23011
23175
  }
23012
23176
  function codingDataRoot(repoRoot) {
23013
- if (!process.env.POLYMATH_DATA_DIR && existsSync5(path38.join(repoRoot, "scripts", "coding-build.mts"))) {
23014
- return path38.join(repoRoot, ".data");
23177
+ if (!process.env.POLYMATH_DATA_DIR && existsSync5(path39.join(repoRoot, "scripts", "coding-build.mts"))) {
23178
+ return path39.join(repoRoot, ".data");
23015
23179
  }
23016
23180
  return resolveDataRoot();
23017
23181
  }
23018
23182
  async function finalCodingArtifacts(codingDir) {
23019
- const files = await fs32.readdir(codingDir).catch(() => []);
23020
- return files.filter((f) => f.endsWith(".json") && f !== "report-run.json" && !f.startsWith(".")).sort().map((f) => path38.join(codingDir, f));
23183
+ const files = await fs33.readdir(codingDir).catch(() => []);
23184
+ return files.filter((f) => f.endsWith(".json") && f !== "report-run.json" && !f.startsWith(".")).sort().map((f) => path39.join(codingDir, f));
23021
23185
  }
23022
23186
  function upToDateSkip(i) {
23023
23187
  const pending2 = i.gradableIds.filter((id) => !i.gradedIds.has(id)).length;
@@ -23030,22 +23194,22 @@ function expectedLlmArtifacts(o) {
23030
23194
  }
23031
23195
  async function evalUpToDate(codingDir, threshold, regrade, opts2) {
23032
23196
  try {
23033
- const dataDir = path38.dirname(codingDir);
23197
+ const dataDir = path39.dirname(codingDir);
23034
23198
  const missing = [];
23035
23199
  for (const rel of expectedLlmArtifacts(opts2 ?? {})) {
23036
- const ok = await fs32.stat(path38.join(dataDir, rel)).then(() => true).catch(() => false);
23200
+ const ok = await fs33.stat(path39.join(dataDir, rel)).then(() => true).catch(() => false);
23037
23201
  if (!ok) missing.push(rel);
23038
23202
  }
23039
23203
  const { partition: partition2, aiWindowCutoff: aiWindowCutoff2, capGradable: capGradable2 } = await Promise.resolve().then(() => (init_gate2(), gate_exports));
23040
- const sessions = JSON.parse(await fs32.readFile(path38.join(codingDir, "sessions.json"), "utf8"));
23204
+ const sessions = JSON.parse(await fs33.readFile(path39.join(codingDir, "sessions.json"), "utf8"));
23041
23205
  const live = sessions.filter((s) => s.klass === "interactive" && !s.duplicateOf);
23042
23206
  const cutoff = aiWindowCutoff2(live);
23043
23207
  const { gradable: uncapped } = partition2(cutoff ? live.filter((s) => (s.start ?? "") >= cutoff) : live);
23044
23208
  const gradable = capGradable2(uncapped);
23045
23209
  const graded = new Set(
23046
- (await fs32.readdir(path38.join(codingDir, "grades")).catch(() => [])).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5))
23210
+ (await fs33.readdir(path39.join(codingDir, "grades")).catch(() => [])).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5))
23047
23211
  );
23048
- const agg = JSON.parse(await fs32.readFile(path38.join(codingDir, "aggregate.json"), "utf8").catch(() => "{}"));
23212
+ const agg = JSON.parse(await fs33.readFile(path39.join(codingDir, "aggregate.json"), "utf8").catch(() => "{}"));
23049
23213
  const hasPriorRun = typeof agg.bigPicture === "string" && agg.bigPicture.trim().length > 0;
23050
23214
  return {
23051
23215
  ...upToDateSkip({
@@ -23110,14 +23274,19 @@ async function runPipeline(o) {
23110
23274
  const dataRoot = codingDataRoot(o.repoRoot);
23111
23275
  const env = { ...process.env, POLYMATH_RESILIENT: "1", POLYMATH_DATA_DIR: dataRoot };
23112
23276
  if (!/max-old-space-size/.test(env.NODE_OPTIONS ?? "")) {
23113
- const headroomMb = Math.min(12288, Math.max(4096, Math.round(os8.totalmem() / 2 ** 20 * 0.6)));
23277
+ const headroomMb = Math.min(12288, Math.max(4096, Math.round(os9.totalmem() / 2 ** 20 * 0.6)));
23114
23278
  env.NODE_OPTIONS = `${env.NODE_OPTIONS ?? ""} --max-old-space-size=${headroomMb}`.trim();
23115
23279
  }
23116
23280
  if (!o.overnight) env.POLYMATH_RESILIENT_NO_WINDOW = "1";
23117
23281
  const stopKeepAwake = o.overnight ? startKeepAwake() : () => {
23118
23282
  };
23119
- const list = stages(o).filter((st) => !o.deterministicOnly || !st.llm);
23120
- const codingDir = path38.join(dataRoot, "coding");
23283
+ const tailModel = await resolveTailModel(o);
23284
+ applyTailModelEnv(env, tailModel);
23285
+ if (tailModel && !o.deterministicOnly) {
23286
+ o.onLine?.(`\u24D8 synthesis stages run on ${tailModel} \u2014 this plan doesn't include Opus (Max plans keep the Opus default)`);
23287
+ }
23288
+ const list = stages({ ...o, tailModel }).filter((st) => !o.deterministicOnly || !st.llm);
23289
+ const codingDir = path39.join(dataRoot, "coding");
23121
23290
  const central = centralConfig();
23122
23291
  const t0 = Date.now();
23123
23292
  const log = await startRunLog(
@@ -23126,7 +23295,7 @@ async function runPipeline(o) {
23126
23295
  supabaseUrl: central.supabaseUrl,
23127
23296
  supabaseAnonKey: central.supabaseAnonKey,
23128
23297
  artifacts: () => finalCodingArtifacts(codingDir),
23129
- refPath: path38.join(codingDir, "report-run.json")
23298
+ refPath: path39.join(codingDir, "report-run.json")
23130
23299
  // next to the served artifacts
23131
23300
  }
23132
23301
  );
@@ -23163,7 +23332,7 @@ async function runPipeline(o) {
23163
23332
  failed.push({ label: st.label, script: st.script, error });
23164
23333
  o.onLine?.(`\u26A0\uFE0F stage FAILED (continuing): ${st.label} \u2014 ${error}`);
23165
23334
  }
23166
- const sha = st.out ? await fileSha(path38.join(dataRoot, st.out)) : null;
23335
+ const sha = st.out ? await fileSha(path39.join(dataRoot, st.out)) : null;
23167
23336
  log.event(st.script.replace(/\.mts$/, ""), { ok, ms: Date.now() - stStart, ...sha ? { sha } : {} });
23168
23337
  stageTimes.push({ script: st.script, label: st.label, ms: Date.now() - stStart, ok });
23169
23338
  o.onStageDone?.(st.label, st.llm, ok, st.script);
@@ -23199,7 +23368,7 @@ async function runPipeline(o) {
23199
23368
  model: o.model ?? "sonnet(+haiku grade)",
23200
23369
  budget: machineBudget()
23201
23370
  };
23202
- await fs32.appendFile(path38.join(codingDir, "run-times.jsonl"), JSON.stringify(record) + "\n");
23371
+ await fs33.appendFile(path39.join(codingDir, "run-times.jsonl"), JSON.stringify(record) + "\n");
23203
23372
  const slowest = stageTimes.filter((s) => !s.skipped).sort((a, b) => b.ms - a.ms).slice(0, 3).map((s) => `${s.script.replace(/^coding-|\.mts$/g, "")} ${fmt(s.ms)}`).join(" \xB7 ");
23204
23373
  o.onLine?.(`\u23F1 run took ${fmt(totalMs)} (${record.kind}) \u2014 slowest: ${slowest} \xB7 ledger: .data/coding/run-times.jsonl`);
23205
23374
  } catch {
@@ -23207,14 +23376,18 @@ async function runPipeline(o) {
23207
23376
  stopKeepAwake();
23208
23377
  return { failed, ...skippedLlm ? { skippedLlm } : {} };
23209
23378
  }
23210
- var MODULE_DIR2, liveStages, killHooked;
23379
+ var MODULE_DIR2, TAIL_MODEL_ENV, liveStages, killHooked;
23211
23380
  var init_pipeline2 = __esm({
23212
23381
  "src/pipeline.ts"() {
23213
23382
  "use strict";
23214
23383
  init_central();
23215
23384
  init_dataHome();
23216
23385
  init_runLog();
23217
- MODULE_DIR2 = path38.dirname(fileURLToPath3(import.meta.url));
23386
+ MODULE_DIR2 = path39.dirname(fileURLToPath3(import.meta.url));
23387
+ TAIL_MODEL_ENV = {
23388
+ "coding-coaching.mts": "AGENT_MODEL",
23389
+ "coding-focus.mts": "FOCUS_JUDGE_MODEL"
23390
+ };
23218
23391
  liveStages = /* @__PURE__ */ new Set();
23219
23392
  killHooked = false;
23220
23393
  }
@@ -23230,9 +23403,9 @@ __export(chatPipeline_exports, {
23230
23403
  runChatPipeline: () => runChatPipeline
23231
23404
  });
23232
23405
  import { spawn as spawn8 } from "child_process";
23233
- import os9 from "os";
23234
- import path39 from "path";
23235
- import { existsSync as existsSync6, promises as fs33 } from "fs";
23406
+ import os10 from "os";
23407
+ import path40 from "path";
23408
+ import { existsSync as existsSync6, promises as fs34 } from "fs";
23236
23409
  import { fileURLToPath as fileURLToPath4 } from "url";
23237
23410
  function chatEngineStages() {
23238
23411
  return [
@@ -23252,40 +23425,40 @@ function chatEngineStages() {
23252
23425
  ];
23253
23426
  }
23254
23427
  function bundledEngineDir() {
23255
- const d = path39.join(MODULE_DIR3, "engine");
23256
- return existsSync6(path39.join(d, "run-tagger.js")) ? d : null;
23428
+ const d = path40.join(MODULE_DIR3, "engine");
23429
+ return existsSync6(path40.join(d, "run-tagger.js")) ? d : null;
23257
23430
  }
23258
23431
  function stageInvocation2(repoRoot, scriptRel, args) {
23259
- const scriptAbs = path39.join(repoRoot, "scripts", scriptRel);
23432
+ const scriptAbs = path40.join(repoRoot, "scripts", scriptRel);
23260
23433
  if (existsSync6(scriptAbs)) {
23261
- const local = path39.join(repoRoot, "node_modules", ".bin", "tsx");
23434
+ const local = path40.join(repoRoot, "node_modules", ".bin", "tsx");
23262
23435
  if (existsSync6(local)) return { cmd: local, argv: [scriptAbs, ...args], cwd: repoRoot };
23263
23436
  return { cmd: "npx", argv: ["tsx", scriptAbs, ...args], cwd: repoRoot };
23264
23437
  }
23265
23438
  const bundled = bundledEngineDir();
23266
23439
  if (bundled) {
23267
- const js = path39.join(bundled, scriptRel.replace(/\.mts$/, ".js"));
23440
+ const js = path40.join(bundled, scriptRel.replace(/\.mts$/, ".js"));
23268
23441
  return { cmd: process.execPath, argv: [js, ...args], cwd: process.cwd() };
23269
23442
  }
23270
23443
  throw new Error(`chat-engine stage ${scriptRel}: neither repo scripts/ nor bundled dist/engine/ found`);
23271
23444
  }
23272
23445
  function hasChatPipeline(repoRoot) {
23273
- return existsSync6(path39.join(repoRoot, "scripts", "run-tagger.mts")) || bundledEngineDir() != null;
23446
+ return existsSync6(path40.join(repoRoot, "scripts", "run-tagger.mts")) || bundledEngineDir() != null;
23274
23447
  }
23275
23448
  function chatDataRoot(repoRoot) {
23276
- if (!process.env.POLYMATH_DATA_DIR && existsSync6(path39.join(repoRoot, "scripts", "run-tagger.mts"))) {
23277
- return path39.join(repoRoot, ".data");
23449
+ if (!process.env.POLYMATH_DATA_DIR && existsSync6(path40.join(repoRoot, "scripts", "run-tagger.mts"))) {
23450
+ return path40.join(repoRoot, ".data");
23278
23451
  }
23279
23452
  return resolveDataRoot();
23280
23453
  }
23281
23454
  async function finalChatArtifacts(dataRoot) {
23282
- const gradesDir = path39.join(dataRoot, "engine-pilot", "grades");
23455
+ const gradesDir = path40.join(dataRoot, "engine-pilot", "grades");
23283
23456
  const out = [];
23284
- for (const lens of (await fs33.readdir(gradesDir).catch(() => [])).sort()) {
23285
- const p = path39.join(gradesDir, lens, "_person.json");
23457
+ for (const lens of (await fs34.readdir(gradesDir).catch(() => [])).sort()) {
23458
+ const p = path40.join(gradesDir, lens, "_person.json");
23286
23459
  if (existsSync6(p)) out.push(p);
23287
23460
  }
23288
- out.push(path39.join(dataRoot, "engine-pilot", "public-report.json"));
23461
+ out.push(path40.join(dataRoot, "engine-pilot", "public-report.json"));
23289
23462
  return out;
23290
23463
  }
23291
23464
  function runStage2(repoRoot, st, env, onLine) {
@@ -23335,7 +23508,7 @@ async function runChatPipeline(o) {
23335
23508
  };
23336
23509
  if (!o.overnight) env.POLYMATH_RESILIENT_NO_WINDOW = "1";
23337
23510
  if (!/max-old-space-size/.test(env.NODE_OPTIONS ?? "")) {
23338
- const headroomMb = Math.min(12288, Math.max(4096, Math.round(os9.totalmem() / 2 ** 20 * 0.6)));
23511
+ const headroomMb = Math.min(12288, Math.max(4096, Math.round(os10.totalmem() / 2 ** 20 * 0.6)));
23339
23512
  env.NODE_OPTIONS = `${env.NODE_OPTIONS ?? ""} --max-old-space-size=${headroomMb}`.trim();
23340
23513
  }
23341
23514
  const failed = [];
@@ -23354,12 +23527,12 @@ async function runChatPipeline(o) {
23354
23527
  supabaseUrl: central.supabaseUrl,
23355
23528
  supabaseAnonKey: central.supabaseAnonKey,
23356
23529
  artifacts: () => finalChatArtifacts(dataRoot),
23357
- refPath: path39.join(dataRoot, "report-run.json")
23530
+ refPath: path40.join(dataRoot, "report-run.json")
23358
23531
  }
23359
23532
  );
23360
23533
  let idx = 0;
23361
23534
  for (const f of ingestable) {
23362
- const st = { script: "ingest-export.mts", label: `Ingest ${f.kind} \u2014 ${path39.basename(f.path)}`, llm: false, args: [f.kind, f.path] };
23535
+ const st = { script: "ingest-export.mts", label: `Ingest ${f.kind} \u2014 ${path40.basename(f.path)}`, llm: false, args: [f.kind, f.path] };
23363
23536
  o.onStage?.(++idx, total, st.label, false);
23364
23537
  const stStart = Date.now();
23365
23538
  let ok = true;
@@ -23376,7 +23549,7 @@ async function runChatPipeline(o) {
23376
23549
  }
23377
23550
  const minNewDocs = Number(process.env.POLYMATH_MIN_NEW_DOCS ?? 10);
23378
23551
  const signalDocs = async () => {
23379
- const t = await fs33.readFile(path39.join(dataRoot, "signals", "index.jsonl"), "utf8").catch(() => "");
23552
+ const t = await fs34.readFile(path40.join(dataRoot, "signals", "index.jsonl"), "utf8").catch(() => "");
23380
23553
  return t ? t.split("\n").filter(Boolean).length : 0;
23381
23554
  };
23382
23555
  const docsBeforeTagger = await signalDocs();
@@ -23398,11 +23571,11 @@ async function runChatPipeline(o) {
23398
23571
  failed.push({ label: st.label, script: st.script, error });
23399
23572
  o.onLine?.(`\u26A0\uFE0F stage FAILED (continuing): ${st.label} \u2014 ${error}`);
23400
23573
  }
23401
- const sha = st.out ? await fileSha(path39.join(dataRoot, st.out)) : null;
23574
+ const sha = st.out ? await fileSha(path40.join(dataRoot, st.out)) : null;
23402
23575
  log.event(st.script.replace(/\.mts$/, ""), { ok, ms: Date.now() - stStart, ...sha ? { sha } : {} });
23403
23576
  if (st.script === "run-tagger.mts" && ok && minNewDocs > 0) {
23404
23577
  const newDocs = await signalDocs() - docsBeforeTagger;
23405
- const report = await fs33.readFile(path39.join(dataRoot, "engine-pilot", "public-report.json"), "utf8").then((s) => JSON.parse(s)).catch(() => null);
23578
+ const report = await fs34.readFile(path40.join(dataRoot, "engine-pilot", "public-report.json"), "utf8").then((s) => JSON.parse(s)).catch(() => null);
23406
23579
  if (report?.headline && (report.spikes?.length ?? 0) > 0 && newDocs < minNewDocs) {
23407
23580
  upToDate = true;
23408
23581
  o.onUpToDate?.();
@@ -23425,7 +23598,7 @@ var init_chatPipeline = __esm({
23425
23598
  init_central();
23426
23599
  init_dataHome();
23427
23600
  init_runLog();
23428
- MODULE_DIR3 = path39.dirname(fileURLToPath4(import.meta.url));
23601
+ MODULE_DIR3 = path40.dirname(fileURLToPath4(import.meta.url));
23429
23602
  INGESTABLE_KINDS = ["chatgpt", "claude", "notion", "obsidian"];
23430
23603
  }
23431
23604
  });
@@ -23635,10 +23808,10 @@ __export(exportScan_exports, {
23635
23808
  scanForExports: () => scanForExports,
23636
23809
  scanForObsidianVaults: () => scanForObsidianVaults
23637
23810
  });
23638
- import { promises as fs34 } from "fs";
23811
+ import { promises as fs35 } from "fs";
23639
23812
  import { execFile as execFile2 } from "child_process";
23640
23813
  import { promisify as promisify2 } from "util";
23641
- import path40 from "path";
23814
+ import path41 from "path";
23642
23815
  function latestPerSource(found) {
23643
23816
  const newest = /* @__PURE__ */ new Map();
23644
23817
  const keyOf = (f) => f.kind === "obsidian" || f.kind === "unknown" ? null : `${f.kind}:${f.account ?? "(unknown-account)"}`;
@@ -23708,7 +23881,7 @@ async function identifyZip(zip, stat) {
23708
23881
  note: ""
23709
23882
  };
23710
23883
  if (!entries) {
23711
- const n = path40.basename(zip).toLowerCase();
23884
+ const n = path41.basename(zip).toLowerCase();
23712
23885
  if (n.includes("chatgpt") || n.includes("openai")) {
23713
23886
  found.kind = "chatgpt";
23714
23887
  found.note = "matched by filename only (no unzip binary to verify)";
@@ -23728,7 +23901,7 @@ async function identifyZip(zip, stat) {
23728
23901
  }
23729
23902
  let { kind, note } = classify2(entries);
23730
23903
  if (kind === "unknown") {
23731
- const nb = path40.basename(zip).toLowerCase();
23904
+ const nb = path41.basename(zip).toLowerCase();
23732
23905
  const nameHint = nb.includes("notion") || /(^|[-_ ])export-/.test(nb);
23733
23906
  const inner = entries.filter((e) => e.toLowerCase().endsWith(".zip"));
23734
23907
  const partZips = inner.some((e) => /export[^/]*part[-_ ]?\d+\.zip$/i.test(e.toLowerCase()));
@@ -23757,17 +23930,17 @@ async function identifyZip(zip, stat) {
23757
23930
  async function identifyDir(dir) {
23758
23931
  let names;
23759
23932
  try {
23760
- names = await fs34.readdir(dir);
23933
+ names = await fs35.readdir(dir);
23761
23934
  } catch {
23762
23935
  return null;
23763
23936
  }
23764
23937
  const { kind, note } = classify2(names);
23765
23938
  if (kind === "unknown") return null;
23766
- const st = await fs34.stat(dir);
23939
+ const st = await fs35.stat(dir);
23767
23940
  const found = { kind, path: dir, account: null, sizeMB: 0, modified: st.mtime.toISOString().slice(0, 10), note };
23768
23941
  const read = async (n) => {
23769
23942
  try {
23770
- return await fs34.readFile(path40.join(dir, n), "utf8");
23943
+ return await fs35.readFile(path41.join(dir, n), "utf8");
23771
23944
  } catch {
23772
23945
  return null;
23773
23946
  }
@@ -23780,7 +23953,7 @@ async function identifyPath(p) {
23780
23953
  if (/\/\.data\//.test(p)) return null;
23781
23954
  let st;
23782
23955
  try {
23783
- st = await fs34.stat(p);
23956
+ st = await fs35.stat(p);
23784
23957
  } catch {
23785
23958
  return null;
23786
23959
  }
@@ -23842,14 +24015,14 @@ async function countMdNotes(root, cap = 2e3) {
23842
24015
  if (n >= cap || depth > 6) return;
23843
24016
  let entries;
23844
24017
  try {
23845
- entries = await fs34.readdir(d, { withFileTypes: true });
24018
+ entries = await fs35.readdir(d, { withFileTypes: true });
23846
24019
  } catch {
23847
24020
  return;
23848
24021
  }
23849
24022
  for (const e of entries) {
23850
24023
  if (n >= cap) return;
23851
24024
  if (e.name.startsWith(".")) continue;
23852
- if (e.isDirectory()) await walk(path40.join(d, e.name), depth + 1);
24025
+ if (e.isDirectory()) await walk(path41.join(d, e.name), depth + 1);
23853
24026
  else if (e.name.endsWith(".md")) n++;
23854
24027
  }
23855
24028
  }
@@ -23858,12 +24031,12 @@ async function countMdNotes(root, cap = 2e3) {
23858
24031
  }
23859
24032
  async function identifyObsidianVault(dir) {
23860
24033
  try {
23861
- const st = await fs34.stat(path40.join(dir, ".obsidian"));
24034
+ const st = await fs35.stat(path41.join(dir, ".obsidian"));
23862
24035
  if (!st.isDirectory()) return null;
23863
24036
  } catch {
23864
24037
  return null;
23865
24038
  }
23866
- const rootSt = await fs34.stat(dir).catch(() => null);
24039
+ const rootSt = await fs35.stat(dir).catch(() => null);
23867
24040
  if (!rootSt) return null;
23868
24041
  const notes = await countMdNotes(dir);
23869
24042
  return {
@@ -23885,21 +24058,21 @@ async function scanForObsidianVaults(home) {
23885
24058
  }
23886
24059
  };
23887
24060
  const roots = [
23888
- path40.join(home, "Library", "Mobile Documents", "iCloud~md~obsidian", "Documents"),
23889
- path40.join(home, "Documents"),
23890
- path40.join(home, "Desktop"),
24061
+ path41.join(home, "Library", "Mobile Documents", "iCloud~md~obsidian", "Documents"),
24062
+ path41.join(home, "Documents"),
24063
+ path41.join(home, "Desktop"),
23891
24064
  home
23892
24065
  ];
23893
24066
  for (const root of roots) {
23894
24067
  let names = [];
23895
24068
  try {
23896
- names = await fs34.readdir(root);
24069
+ names = await fs35.readdir(root);
23897
24070
  } catch {
23898
24071
  continue;
23899
24072
  }
23900
24073
  for (const name17 of names) {
23901
24074
  if (name17.startsWith(".")) continue;
23902
- add(await identifyObsidianVault(path40.join(root, name17)));
24075
+ add(await identifyObsidianVault(path41.join(root, name17)));
23903
24076
  }
23904
24077
  }
23905
24078
  return out;
@@ -23908,16 +24081,16 @@ async function scanForExports(root) {
23908
24081
  const out = [];
23909
24082
  let names;
23910
24083
  try {
23911
- names = await fs34.readdir(root);
24084
+ names = await fs35.readdir(root);
23912
24085
  } catch {
23913
24086
  return out;
23914
24087
  }
23915
24088
  for (const name17 of names) {
23916
24089
  if (name17.startsWith(".")) continue;
23917
- const p = path40.join(root, name17);
24090
+ const p = path41.join(root, name17);
23918
24091
  let st;
23919
24092
  try {
23920
- st = await fs34.stat(p);
24093
+ st = await fs35.stat(p);
23921
24094
  } catch {
23922
24095
  continue;
23923
24096
  }
@@ -24045,16 +24218,16 @@ __export(reminderState_exports, {
24045
24218
  recordQueued: () => recordQueued,
24046
24219
  trackedLinkUrl: () => trackedLinkUrl
24047
24220
  });
24048
- import { promises as fs35 } from "node:fs";
24049
- import path41 from "node:path";
24221
+ import { promises as fs36 } from "node:fs";
24222
+ import path42 from "node:path";
24050
24223
  import crypto4 from "node:crypto";
24051
24224
  function newToken() {
24052
24225
  return crypto4.randomBytes(12).toString("base64url");
24053
24226
  }
24054
24227
  async function ensureReminderState(dataDir) {
24055
- const file2 = path41.join(dataDir, FILE);
24228
+ const file2 = path42.join(dataDir, FILE);
24056
24229
  try {
24057
- const s = JSON.parse(await fs35.readFile(file2, "utf8"));
24230
+ const s = JSON.parse(await fs36.readFile(file2, "utf8"));
24058
24231
  if (typeof s.token === "string" && s.token.length >= 8) {
24059
24232
  return {
24060
24233
  token: s.token,
@@ -24068,8 +24241,8 @@ async function ensureReminderState(dataDir) {
24068
24241
  }
24069
24242
  const state = { token: newToken(), queuedKinds: [] };
24070
24243
  try {
24071
- await fs35.mkdir(dataDir, { recursive: true });
24072
- await fs35.writeFile(file2, JSON.stringify(state));
24244
+ await fs36.mkdir(dataDir, { recursive: true });
24245
+ await fs36.writeFile(file2, JSON.stringify(state));
24073
24246
  } catch {
24074
24247
  }
24075
24248
  return state;
@@ -24084,8 +24257,8 @@ async function recordQueued(dataDir, state, kinds) {
24084
24257
  queuedAt: (/* @__PURE__ */ new Date()).toISOString()
24085
24258
  };
24086
24259
  try {
24087
- await fs35.mkdir(dataDir, { recursive: true });
24088
- await fs35.writeFile(path41.join(dataDir, FILE), JSON.stringify(next));
24260
+ await fs36.mkdir(dataDir, { recursive: true });
24261
+ await fs36.writeFile(path42.join(dataDir, FILE), JSON.stringify(next));
24089
24262
  } catch {
24090
24263
  }
24091
24264
  return next;
@@ -24098,8 +24271,8 @@ async function recordOffered(dataDir, state, kinds) {
24098
24271
  offeredAt: (/* @__PURE__ */ new Date()).toISOString()
24099
24272
  };
24100
24273
  try {
24101
- await fs35.mkdir(dataDir, { recursive: true });
24102
- await fs35.writeFile(path41.join(dataDir, FILE), JSON.stringify(next));
24274
+ await fs36.mkdir(dataDir, { recursive: true });
24275
+ await fs36.writeFile(path42.join(dataDir, FILE), JSON.stringify(next));
24103
24276
  } catch {
24104
24277
  }
24105
24278
  return next;
@@ -24116,151 +24289,6 @@ var init_reminderState = __esm({
24116
24289
  }
24117
24290
  });
24118
24291
 
24119
- // src/plan.ts
24120
- var plan_exports = {};
24121
- __export(plan_exports, {
24122
- UNSUPPORTED_FULL_TIERS: () => UNSUPPORTED_FULL_TIERS,
24123
- detectCodexPlan: () => detectCodexPlan,
24124
- detectPlan: () => detectPlan,
24125
- estimateLaneCosts: () => estimateLaneCosts,
24126
- remainingFractions: () => remainingFractions,
24127
- scheduleLanes: () => scheduleLanes,
24128
- supportsFullAnalysis: () => supportsFullAnalysis,
24129
- tierFromChatgptPlan: () => tierFromChatgptPlan,
24130
- tierFromStrings: () => tierFromStrings
24131
- });
24132
- import { promises as fs36 } from "fs";
24133
- import os10 from "os";
24134
- import path42 from "path";
24135
- function supportsFullAnalysis(tier) {
24136
- if (process.env.POLYMATH_FORCE_FULL === "1") return true;
24137
- return !UNSUPPORTED_FULL_TIERS.includes(tier);
24138
- }
24139
- function tierFromStrings(...hints) {
24140
- for (const h of hints) {
24141
- if (!h) continue;
24142
- const s = h.toLowerCase();
24143
- if (s.includes("max_20x") || s.includes("max20x")) return "max_20x";
24144
- if (s.includes("max_5x") || s.includes("max5x")) return "max_5x";
24145
- if (s.includes("pro")) return "pro";
24146
- if (s.includes("claude_max")) return "max_5x";
24147
- }
24148
- return "unknown";
24149
- }
24150
- function tierFromChatgptPlan(planType) {
24151
- const s = (planType ?? "").toLowerCase();
24152
- if (s === "pro") return "gpt_pro";
24153
- if (s === "plus") return "gpt_plus";
24154
- if (s === "team" || s === "enterprise" || s === "business") return "gpt_team";
24155
- if (s === "free") return "gpt_free";
24156
- return "unknown";
24157
- }
24158
- async function detectCodexPlan(home = os10.homedir()) {
24159
- try {
24160
- const raw = JSON.parse(await fs36.readFile(path42.join(home, ".codex", "auth.json"), "utf8"));
24161
- for (const tok of [raw.tokens?.id_token, raw.tokens?.access_token]) {
24162
- if (!tok) continue;
24163
- try {
24164
- const payload = JSON.parse(Buffer.from(tok.split(".")[1], "base64url").toString("utf8"));
24165
- const t = tierFromChatgptPlan(payload["https://api.openai.com/auth"]?.chatgpt_plan_type);
24166
- if (t !== "unknown") return t;
24167
- } catch {
24168
- }
24169
- }
24170
- } catch {
24171
- }
24172
- return "unknown";
24173
- }
24174
- async function detectPlan(home = os10.homedir()) {
24175
- let tier = "unknown";
24176
- try {
24177
- const raw = JSON.parse(await fs36.readFile(path42.join(home, ".claude.json"), "utf8"));
24178
- const oa = raw.oauthAccount ?? {};
24179
- tier = tierFromStrings(oa.userRateLimitTier, oa.organizationRateLimitTier, oa.organizationType);
24180
- } catch {
24181
- }
24182
- if (tier === "unknown") tier = await detectCodexPlan(home);
24183
- return { tier, label: LABEL[tier], windowUsd: WINDOW_USD[tier] };
24184
- }
24185
- function estimateLaneCosts(input) {
24186
- const codingUsd = Math.max(5, Math.min(32, Math.round(input.agentLogFiles * 9e-4)));
24187
- const chatUsd = input.chatIncluded ? Math.round(40 + input.exportTotalMB * 0.8) : 0;
24188
- return { codingUsd, chatUsd };
24189
- }
24190
- async function remainingFractions(dataRoot, agentLogFiles) {
24191
- const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
24192
- let coding = 1;
24193
- try {
24194
- const graded = (await fs36.readdir(path42.join(dataRoot, "coding", "grades"))).filter((f) => f.endsWith(".json")).length;
24195
- const expected = Math.max(1, agentLogFiles * 56e-4);
24196
- coding = clamp(1 - graded / expected, 0.05, 1);
24197
- } catch {
24198
- }
24199
- let chatDone = 0;
24200
- try {
24201
- const st = await fs36.stat(path42.join(dataRoot, "signals", "index.jsonl"));
24202
- if (st.size > 0) chatDone += 0.3;
24203
- } catch {
24204
- }
24205
- try {
24206
- const lenses = await fs36.readdir(path42.join(dataRoot, "engine-pilot", "grades"));
24207
- let withPerson = 0;
24208
- for (const l of lenses) {
24209
- try {
24210
- await fs36.stat(path42.join(dataRoot, "engine-pilot", "grades", l, "_person.json"));
24211
- withPerson++;
24212
- } catch {
24213
- }
24214
- }
24215
- chatDone += 0.6 * Math.min(1, withPerson / 5);
24216
- } catch {
24217
- }
24218
- return { coding, chat: clamp(1 - chatDone, 0.1, 1) };
24219
- }
24220
- function scheduleLanes(plan, est) {
24221
- const chat = est.chatUsd === 0 || est.chatUsd <= plan.windowUsd * 0.9 ? "now" : "overnight";
24222
- return {
24223
- coding: "now",
24224
- chat,
24225
- // No plan name here — the schedule header names the plan once, and
24226
- // repeating it doubled the clumsy unknown-plan label (2026-07-22
24227
- // walkthrough). Phrased for the two-column "Proposed schedule" rows:
24228
- // "<lane> <verdict> — <reason>".
24229
- codingReason: `runs now \u2014 the report link appears right away and fills in as it goes`,
24230
- chatReason: est.chatUsd === 0 ? "not planned \u2014 no chat sources included" : chat === "overnight" ? `starts at 2:00 am tonight \u2014 a chat corpus this size is an overnight job; it runs while you sleep` : `runs now, alongside coding`
24231
- };
24232
- }
24233
- var UNSUPPORTED_FULL_TIERS, WINDOW_USD, LABEL;
24234
- var init_plan = __esm({
24235
- "src/plan.ts"() {
24236
- "use strict";
24237
- UNSUPPORTED_FULL_TIERS = ["pro", "gpt_plus", "gpt_free"];
24238
- WINDOW_USD = {
24239
- max_20x: 300,
24240
- max_5x: 75,
24241
- pro: 15,
24242
- // ChatGPT tiers, mapped to rough Claude-window parity by monthly price
24243
- // ($200 Pro ≈ Max 20x; $20 Plus ≈ Claude Pro; Team between; free ≈ nothing).
24244
- gpt_pro: 300,
24245
- gpt_plus: 20,
24246
- gpt_team: 50,
24247
- gpt_free: 5,
24248
- unknown: 15
24249
- // conservative: schedule like Pro when we can't tell
24250
- };
24251
- LABEL = {
24252
- max_20x: "Claude Max 20x",
24253
- max_5x: "Claude Max 5x",
24254
- pro: "Claude Pro",
24255
- gpt_pro: "ChatGPT Pro (Codex)",
24256
- gpt_plus: "ChatGPT Plus (Codex)",
24257
- gpt_team: "ChatGPT Team (Codex)",
24258
- gpt_free: "ChatGPT Free (Codex)",
24259
- unknown: "an undetected plan (scheduling conservatively)"
24260
- };
24261
- }
24262
- });
24263
-
24264
24292
  // src/wizard.ts
24265
24293
  var wizard_exports = {};
24266
24294
  __export(wizard_exports, {
@@ -24654,13 +24682,52 @@ async function runWizard() {
24654
24682
  say(` ${bold("Proposed schedule")} \u2014 sized to your plan (${plan.label}):`);
24655
24683
  say();
24656
24684
  say(` ${bold("Your analysis")} runs now \u2014 everything measured from your logs: words/day, flow state, focus, projects, parallelism`);
24657
- const planPhrase = plan.tier === "pro" ? "the $20 Claude Pro plan" : plan.tier === "gpt_plus" ? "the $20 ChatGPT Plus plan" : "the free ChatGPT plan";
24685
+ const planPhrase = plan.tier === "gpt_plus" ? "the $20 ChatGPT Plus plan" : "the free ChatGPT plan";
24658
24686
  say(` ${bold("AI sections")} not supported on ${planPhrase} just yet \u2014 coming soon! Only the AI-graded sections (judgement, agency, taste, growth) won't be visible`);
24659
24687
  say();
24660
24688
  await ask(` Press Enter to run your analysis`).catch(() => "");
24661
24689
  say(dim(" On it. The report link appears right away.\n"));
24662
24690
  return { cmd: "serve", grade: false, overnight: false, chatOvernight: false, open: true };
24663
24691
  }
24692
+ if (plan.tier === "pro") {
24693
+ say(` ${bold("Proposed schedule")} \u2014 sized to your plan (${plan.label}):`);
24694
+ if (resumed) say(dim(" (an earlier run already processed most of this \u2014 only what's new is scheduled; nothing done is redone)"));
24695
+ say();
24696
+ say(` ${bold("Deterministic sections")} run now \u2014 everything measured from your logs: words/day, flow state, focus, projects, parallelism`);
24697
+ say();
24698
+ say(` ${bold("AI sections")} run overnight, starting 1:00 am \u2014 grades, judgement, agency, taste, growth`);
24699
+ say();
24700
+ say(` Why overnight: a full AI run costs about two of your plan's 5-hour usage windows.`);
24701
+ say(` Running it at night spends windows you're asleep for \u2014 it hits the limit around`);
24702
+ say(` halfway, pauses, and resumes on its own when the window resets (~6:00 am).`);
24703
+ say();
24704
+ say(` ${bold("1) Run on this schedule \u2014 RECOMMENDED")}`);
24705
+ say(` \u2022 keep your laptop plugged in`);
24706
+ say(` \u2022 keep the lid open \u2014 closed means sleep, and the run stalls until morning`);
24707
+ say(` ${bold("2)")} Run the AI sections now instead \u2014 you'll likely hit your plan's usage limit partway;`);
24708
+ say(` the run pauses and resumes by itself when the limit resets (~5h), and the report`);
24709
+ say(` link works the whole time, filling in as stages finish`);
24710
+ say();
24711
+ const proDepth = await ask(` Choose ${dim("[1/2, default 1]")}: `) || "1";
24712
+ const proOvernight = proDepth !== "2";
24713
+ if (proOvernight) {
24714
+ if (!process.env.POLYMATH_NIGHTLY_START) process.env.POLYMATH_NIGHTLY_START = "1";
24715
+ say(` ${bold("VERY IMPORTANT for the overnight part:")}`);
24716
+ say(` \u2022 keep your laptop plugged in`);
24717
+ say(` \u2022 keep the lid open \u2014 closed means sleep, and the run stalls until morning`);
24718
+ say(dim(` (the report page shows this too)`));
24719
+ }
24720
+ say(dim(" On it. The report link appears right away and fills in as stages finish \u2014 Ctrl-C anytime.\n"));
24721
+ return {
24722
+ cmd: "serve",
24723
+ grade: true,
24724
+ overnight: proOvernight,
24725
+ // The chat corpus is unbounded (~$390 measured on 450 MB) — on a $15
24726
+ // window it is ALWAYS an overnight job, whatever the coding choice.
24727
+ chatOvernight: est.chatUsd > 0 ? true : proOvernight,
24728
+ open: true
24729
+ };
24730
+ }
24664
24731
  const planBit = plan.label.includes("undetected") ? "your corpus (plan undetected \u2014 scheduling conservatively)" : `your plan (${plan.label}) and your corpus`;
24665
24732
  say(` ${bold("Proposed schedule")} \u2014 sized to ${planBit}:`);
24666
24733
  if (resumed) say(dim(" (an earlier run already processed most of this \u2014 only what's new is scheduled; nothing done is redone)"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polymath-society",
3
- "version": "0.2.30",
3
+ "version": "0.2.31",
4
4
  "description": "Deterministic metrics from your local Claude Code / Codex agent logs — parallelism, flow, throughput, projects. Zero LLM, zero server, on-device.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",