polymath-society 0.2.25 → 0.2.26

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
@@ -22834,6 +22834,8 @@ __export(pipeline_exports, {
22834
22834
  runPipeline: () => runPipeline,
22835
22835
  stages: () => stages,
22836
22836
  startKeepAwake: () => startKeepAwake,
22837
+ trackStageChild: () => trackStageChild,
22838
+ untrackStageChild: () => untrackStageChild,
22837
22839
  upToDateSkip: () => upToDateSkip
22838
22840
  });
22839
22841
  import { spawn as spawn7 } from "child_process";
@@ -22947,18 +22949,54 @@ function stageInvocation(repoRoot, scriptRel, args) {
22947
22949
  }
22948
22950
  throw new Error(`pipeline stage ${scriptRel}: neither repo scripts/ nor bundled dist/pipeline/ found`);
22949
22951
  }
22952
+ function killLiveStages() {
22953
+ for (const c of liveStages) {
22954
+ try {
22955
+ if (process.platform === "win32" || !c.pid) c.kill("SIGKILL");
22956
+ else process.kill(-c.pid, "SIGKILL");
22957
+ } catch {
22958
+ }
22959
+ }
22960
+ liveStages.clear();
22961
+ }
22962
+ function hookStageKill() {
22963
+ if (killHooked) return;
22964
+ killHooked = true;
22965
+ process.on("exit", killLiveStages);
22966
+ for (const sig of ["SIGINT", "SIGTERM"]) {
22967
+ process.on(sig, () => {
22968
+ killLiveStages();
22969
+ process.exit(sig === "SIGINT" ? 130 : 143);
22970
+ });
22971
+ }
22972
+ }
22973
+ function trackStageChild(child) {
22974
+ hookStageKill();
22975
+ liveStages.add(child);
22976
+ }
22977
+ function untrackStageChild(child) {
22978
+ liveStages.delete(child);
22979
+ }
22950
22980
  function runStage(repoRoot, st, env, onLine) {
22951
22981
  return new Promise((resolve2, reject) => {
22952
22982
  const { cmd, argv, cwd } = stageInvocation(repoRoot, st.script, st.args);
22953
- const child = spawn7(cmd, argv, { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
22983
+ hookStageKill();
22984
+ const child = spawn7(cmd, argv, { cwd, env, stdio: ["ignore", "pipe", "pipe"], detached: process.platform !== "win32" });
22985
+ liveStages.add(child);
22954
22986
  const pump = (buf) => {
22955
22987
  if (!onLine) return;
22956
22988
  for (const line of buf.toString().split(/\r?\n/)) if (line.trim()) onLine(line);
22957
22989
  };
22958
22990
  child.stdout.on("data", pump);
22959
22991
  child.stderr.on("data", pump);
22960
- child.on("error", reject);
22961
- child.on("close", (code) => code === 0 ? resolve2() : reject(new Error(`${st.script} exited with code ${code}`)));
22992
+ child.on("error", (e) => {
22993
+ liveStages.delete(child);
22994
+ reject(e);
22995
+ });
22996
+ child.on("close", (code) => {
22997
+ liveStages.delete(child);
22998
+ code === 0 ? resolve2() : reject(new Error(`${st.script} exited with code ${code}`));
22999
+ });
22962
23000
  });
22963
23001
  }
22964
23002
  function hasPipeline(repoRoot) {
@@ -23099,6 +23137,7 @@ async function runPipeline(o) {
23099
23137
  o.onStage?.(++stageNo, list.length, `${st.label} \u2014 up to date, skipped`, st.llm);
23100
23138
  log.event(st.script.replace(/\.mts$/, ""), { ok: true, ms: 0, skipped: true });
23101
23139
  stageTimes.push({ script: st.script, label: st.label, ms: 0, ok: true, skipped: true });
23140
+ o.onStageDone?.(st.label, st.llm, true, st.script);
23102
23141
  return;
23103
23142
  }
23104
23143
  o.onStage?.(++stageNo, list.length, group.length > 1 ? `${st.label} (parallel \xD7${group.length})` : st.label, st.llm);
@@ -23116,6 +23155,7 @@ async function runPipeline(o) {
23116
23155
  const sha = st.out ? await fileSha(path38.join(dataRoot, st.out)) : null;
23117
23156
  log.event(st.script.replace(/\.mts$/, ""), { ok, ms: Date.now() - stStart, ...sha ? { sha } : {} });
23118
23157
  stageTimes.push({ script: st.script, label: st.label, ms: Date.now() - stStart, ok });
23158
+ o.onStageDone?.(st.label, st.llm, ok, st.script);
23119
23159
  }));
23120
23160
  if (!o.deterministicOnly && !skippedLlm && group[0].script === "coding-build.mts") {
23121
23161
  const gate2 = await evalUpToDate(codingDir, threshold, o.regrade, o);
@@ -23156,7 +23196,7 @@ async function runPipeline(o) {
23156
23196
  stopKeepAwake();
23157
23197
  return { failed, ...skippedLlm ? { skippedLlm } : {} };
23158
23198
  }
23159
- var MODULE_DIR2;
23199
+ var MODULE_DIR2, liveStages, killHooked;
23160
23200
  var init_pipeline2 = __esm({
23161
23201
  "src/pipeline.ts"() {
23162
23202
  "use strict";
@@ -23164,6 +23204,8 @@ var init_pipeline2 = __esm({
23164
23204
  init_dataHome();
23165
23205
  init_runLog();
23166
23206
  MODULE_DIR2 = path38.dirname(fileURLToPath3(import.meta.url));
23207
+ liveStages = /* @__PURE__ */ new Set();
23208
+ killHooked = false;
23167
23209
  }
23168
23210
  });
23169
23211
 
@@ -23237,15 +23279,22 @@ async function finalChatArtifacts(dataRoot) {
23237
23279
  function runStage2(repoRoot, st, env, onLine) {
23238
23280
  return new Promise((resolve2, reject) => {
23239
23281
  const { cmd, argv, cwd } = stageInvocation2(repoRoot, st.script, st.args);
23240
- const child = spawn8(cmd, argv, { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
23282
+ const child = spawn8(cmd, argv, { cwd, env, stdio: ["ignore", "pipe", "pipe"], detached: process.platform !== "win32" });
23283
+ trackStageChild(child);
23241
23284
  const pump = (buf) => {
23242
23285
  if (!onLine) return;
23243
23286
  for (const line of buf.toString().split(/\r?\n/)) if (line.trim()) onLine(line);
23244
23287
  };
23245
23288
  child.stdout.on("data", pump);
23246
23289
  child.stderr.on("data", pump);
23247
- child.on("error", reject);
23248
- child.on("close", (code) => code === 0 ? resolve2() : reject(new Error(`${st.script} exited with code ${code}`)));
23290
+ child.on("error", (e) => {
23291
+ untrackStageChild(child);
23292
+ reject(e);
23293
+ });
23294
+ child.on("close", (code) => {
23295
+ untrackStageChild(child);
23296
+ code === 0 ? resolve2() : reject(new Error(`${st.script} exited with code ${code}`));
23297
+ });
23249
23298
  });
23250
23299
  }
23251
23300
  async function runChatPipeline(o) {
@@ -23356,6 +23405,7 @@ var init_chatPipeline = __esm({
23356
23405
  "src/chatPipeline.ts"() {
23357
23406
  "use strict";
23358
23407
  init_manifest();
23408
+ init_pipeline2();
23359
23409
  init_central();
23360
23410
  init_dataHome();
23361
23411
  init_runLog();
@@ -24823,9 +24873,9 @@ function makeMultiBar(out = process.stderr) {
24823
24873
  const width = 16;
24824
24874
  const elapsedMin = Math.round((Date.now() - t0) / 6e4);
24825
24875
  const laneRow = ([name17, l]) => {
24826
- const real = l.within && l.within.total ? l.within.done / l.within.total : 0;
24876
+ const real = [...l.fracs.values()].reduce((a, f) => a + (f.total ? Math.min(1, f.done / f.total) : 0), 0);
24827
24877
  const creep = 0.9 * (1 - Math.exp(-(Date.now() - l.stageAt) / 9e4));
24828
- const withinFrac = Math.min(1, Math.max(real, l.i >= 1 && l.i <= l.total ? creep : 0));
24878
+ const withinFrac = Math.min(Math.max(0, l.total - (l.i - 1)), Math.max(real, l.i >= 1 && l.i <= l.total ? creep : 0));
24829
24879
  const frac = l.total ? (l.i - 1 + withinFrac) / l.total : 0;
24830
24880
  l.shown = Math.max(l.shown, Math.max(0, Math.min(1, frac)));
24831
24881
  const fill = Math.round(l.shown * width);
@@ -24866,36 +24916,49 @@ function makeMultiBar(out = process.stderr) {
24866
24916
  };
24867
24917
  return {
24868
24918
  lane(name17) {
24919
+ const getLane = () => {
24920
+ const l = lanes.get(name17) ?? { i: 0, total: 0, label: "", fracs: /* @__PURE__ */ new Map(), stageAt: Date.now(), shown: 0 };
24921
+ lanes.set(name17, l);
24922
+ return l;
24923
+ };
24869
24924
  return {
24870
24925
  stage(i, total, label) {
24871
- const l = lanes.get(name17) ?? { i: 0, total: 0, label: "", within: null, stageAt: Date.now(), shown: 0 };
24926
+ const l = getLane();
24872
24927
  l.i = i;
24873
24928
  l.total = total;
24874
24929
  l.label = label;
24875
- l.within = null;
24876
24930
  l.stageAt = Date.now();
24877
- lanes.set(name17, l);
24931
+ for (const [k, f] of l.fracs) if (f.total && f.done >= f.total) l.fracs.delete(k);
24878
24932
  repaintAround(`\u25B8 ${name17} [${i}/${total}] ${label}`);
24879
24933
  },
24880
24934
  log(line) {
24881
24935
  const l = lanes.get(name17);
24882
24936
  if (l) {
24883
24937
  const m = /Σ\s+(\d+)\/(\d+)/.exec(line);
24884
- if (m) l.within = { done: Number(m[1]), total: Number(m[2]) };
24938
+ if (m) {
24939
+ const tag = /^\s*\[([^\]]+)\]/.exec(line)?.[1] ?? "_";
24940
+ l.fracs.set(tag, { done: Number(m[1]), total: Number(m[2]) });
24941
+ }
24885
24942
  }
24886
24943
  repaintAround(tty ? line : `[${name17}] ${line.trimStart()}`);
24887
24944
  },
24945
+ // A stage completed: its share now lives in `i`, so drop its running
24946
+ // fraction (fall back to the untagged slot for single stages).
24947
+ stageDone(key2) {
24948
+ const l = lanes.get(name17);
24949
+ if (l && !l.fracs.delete(key2)) l.fracs.delete("_");
24950
+ if (tty) repaintAround();
24951
+ },
24888
24952
  // Pin the lane at 100% immediately (up-to-date gate: the report is
24889
24953
  // ready NOW; remaining stages are silent housekeeping and must not
24890
24954
  // crawl the bar through partial percentages).
24891
24955
  finish(label) {
24892
- const l = lanes.get(name17) ?? { i: 0, total: 0, label: "", within: null, stageAt: Date.now(), shown: 0 };
24956
+ const l = getLane();
24893
24957
  l.total = l.total || 1;
24894
24958
  l.i = l.total + 1;
24895
24959
  l.label = label;
24896
- l.within = null;
24960
+ l.fracs.clear();
24897
24961
  l.shown = 1;
24898
- lanes.set(name17, l);
24899
24962
  repaintAround(`\u25B8 ${name17} \u2713 ${label}`);
24900
24963
  }
24901
24964
  };
@@ -27998,7 +28061,14 @@ function startServer(opts2) {
27998
28061
  // through 127.0.0.1 no matter which name the person's tab uses.
27999
28062
  url: host === "127.0.0.1" ? `http://localhost:${port}` : serverUrl,
28000
28063
  port,
28001
- close: () => new Promise((r) => server.close(() => r())),
28064
+ // closeAllConnections BEFORE close: server.close() waits for every
28065
+ // open socket to drain, and the report tab holds a live progress
28066
+ // connection — an awaited close made Ctrl+C appear completely dead
28067
+ // (owner report 2026-07-22).
28068
+ close: () => new Promise((r) => {
28069
+ server.closeAllConnections?.();
28070
+ server.close(() => r());
28071
+ }),
28002
28072
  setProfile: (p) => {
28003
28073
  liveProfile = p;
28004
28074
  profileRev++;
@@ -28380,17 +28450,22 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
28380
28450
  }, Number(process.env.POLYMATH_LIVE_REFRESH_MS || 45e3));
28381
28451
  const { makeMultiBar: makeMultiBar2 } = await Promise.resolve().then(() => (init_progressBar(), progressBar_exports));
28382
28452
  const mbar = makeMultiBar2();
28453
+ const withinFracs = { coding: /* @__PURE__ */ new Map(), chat: /* @__PURE__ */ new Map() };
28454
+ const sumFracs = (lane) => [...withinFracs[lane].values()].reduce((a, f) => a + (f.total ? Math.min(1, f.done / f.total) : 0), 0);
28383
28455
  const harvestWithin = (lane, line) => {
28384
28456
  const m = /Σ\s+(\d+)\/(\d+)/.exec(line);
28385
28457
  if (m && progress[lane] && !progress[lane].done) {
28386
- progress[lane].within = { done: Number(m[1]), total: Number(m[2]) };
28458
+ const tag = /^\s*\[([^\]]+)\]/.exec(line)?.[1] ?? "_";
28459
+ withinFracs[lane].set(tag, { done: Number(m[1]), total: Number(m[2]) });
28460
+ progress[lane].withinFrac = Math.round(sumFracs(lane) * 1e3) / 1e3;
28387
28461
  earlyHandle.setProgress(progress);
28388
28462
  }
28389
28463
  };
28390
28464
  mbar.setLink(earlyHandle.url);
28391
28465
  const codingLane = mbar.lane("coding");
28392
28466
  const aiCount = { coding: 0, chat: 0 };
28393
- const aiSeen = { coding: 0, chat: 0 };
28467
+ const aiStarted = { coding: 0, chat: 0 };
28468
+ const aiDone = { coding: 0, chat: 0 };
28394
28469
  let codingPinned = false;
28395
28470
  if (await readPriorAnalysis()) {
28396
28471
  codingPinned = true;
@@ -28427,11 +28502,20 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
28427
28502
  },
28428
28503
  onStage: (i, total, label, llm) => {
28429
28504
  if (codingPinned) return;
28430
- if (llm) aiSeen.coding++;
28431
- progress.coding = { ...progress.coding, i: aiSeen.coding, total: aiCount.coding, label, done: false, within: void 0 };
28505
+ if (llm) aiStarted.coding++;
28506
+ progress.coding = { ...progress.coding, i: llm ? aiDone.coding + 1 : aiDone.coding, total: aiCount.coding, label, done: false };
28507
+ earlyHandle.setProgress(progress);
28508
+ if (llm) codingLane.stage(aiDone.coding + 1, aiCount.coding, `${label} (AI)`);
28509
+ else if (aiStarted.coding === 0) codingLane.stage(0, aiCount.coding, label);
28510
+ },
28511
+ onStageDone: (label, llm, _ok, script) => {
28512
+ if (codingPinned || !llm) return;
28513
+ aiDone.coding++;
28514
+ const key2 = script.replace(/^coding-|\.mts$/g, "");
28515
+ if (!withinFracs.coding.delete(key2)) withinFracs.coding.delete("_");
28516
+ codingLane.stageDone(key2);
28517
+ progress.coding = { ...progress.coding, i: Math.min(aiDone.coding + 1, aiCount.coding), total: aiCount.coding, done: false, withinFrac: Math.round(sumFracs("coding") * 1e3) / 1e3 };
28432
28518
  earlyHandle.setProgress(progress);
28433
- if (llm) codingLane.stage(aiSeen.coding, aiCount.coding, `${label} (AI)`);
28434
- else if (aiSeen.coding === 0) codingLane.stage(0, aiCount.coding, label);
28435
28519
  },
28436
28520
  // While pinned, the scrolling stage output stays quiet ("prints hella
28437
28521
  // things when everything is cached", owner 2026-07-17) — warnings and
@@ -28503,11 +28587,14 @@ Running the full analysis on ${backend.name === "cli" ? "Claude Code" : "Codex"}
28503
28587
  },
28504
28588
  onStage: (i, total, label, llm) => {
28505
28589
  if (chatPinned) return;
28506
- if (llm) aiSeen.chat++;
28507
- progress.chat = { ...progress.chat, i: aiSeen.chat, total: aiCount.chat, label, done: false, within: void 0 };
28590
+ if (llm) {
28591
+ aiStarted.chat++;
28592
+ withinFracs.chat.clear();
28593
+ }
28594
+ progress.chat = { ...progress.chat, i: aiStarted.chat, total: aiCount.chat, label, done: false, withinFrac: void 0 };
28508
28595
  earlyHandle.setProgress(progress);
28509
- if (llm) chatLane.stage(aiSeen.chat, aiCount.chat, `${label} (AI)`);
28510
- else if (aiSeen.chat === 0) chatLane.stage(0, aiCount.chat, label);
28596
+ if (llm) chatLane.stage(aiStarted.chat, aiCount.chat, `${label} (AI)`);
28597
+ else if (aiStarted.chat === 0) chatLane.stage(0, aiCount.chat, label);
28511
28598
  },
28512
28599
  onLine: (line) => {
28513
28600
  harvestWithin("chat", line);
@@ -28633,14 +28720,20 @@ No analysis here yet \u2014 computing the instant deterministic metrics now (fre
28633
28720
  console.error(` (Ctrl-C to stop)
28634
28721
  `);
28635
28722
  if (opts2.open && !earlyHandle) openBrowser(handle.url);
28636
- process.on("SIGINT", async () => {
28637
- await handle.close();
28638
- process.exit(0);
28639
- });
28640
- process.on("SIGTERM", async () => {
28641
- await handle.close();
28642
- process.exit(0);
28643
- });
28723
+ let dying = false;
28724
+ const shutdown = (code) => async () => {
28725
+ if (dying) process.exit(code);
28726
+ dying = true;
28727
+ const hardExit = setTimeout(() => process.exit(code), 1e3);
28728
+ if (typeof hardExit.unref === "function") hardExit.unref();
28729
+ try {
28730
+ await handle.close();
28731
+ } catch {
28732
+ }
28733
+ process.exit(code);
28734
+ };
28735
+ process.on("SIGINT", shutdown(130));
28736
+ process.on("SIGTERM", shutdown(143));
28644
28737
  return;
28645
28738
  }
28646
28739
  const profile = await analyze({ codex: opts2.codex, idleGapMin: opts2.idleGapMin, tz: opts2.tz, spanHourMin: opts2.spanHourMin });
package/dist/index.js CHANGED
@@ -25606,7 +25606,14 @@ function startServer(opts2) {
25606
25606
  // through 127.0.0.1 no matter which name the person's tab uses.
25607
25607
  url: host === "127.0.0.1" ? `http://localhost:${port}` : serverUrl,
25608
25608
  port,
25609
- close: () => new Promise((r) => server.close(() => r())),
25609
+ // closeAllConnections BEFORE close: server.close() waits for every
25610
+ // open socket to drain, and the report tab holds a live progress
25611
+ // connection — an awaited close made Ctrl+C appear completely dead
25612
+ // (owner report 2026-07-22).
25613
+ close: () => new Promise((r) => {
25614
+ server.closeAllConnections?.();
25615
+ server.close(() => r());
25616
+ }),
25610
25617
  setProfile: (p) => {
25611
25618
  liveProfile = p;
25612
25619
  profileRev++;
package/dist/web/app.js CHANGED
@@ -12939,9 +12939,9 @@ function laneFrac(name, l) {
12939
12939
  m.label = l.label;
12940
12940
  m.at = Date.now();
12941
12941
  }
12942
- const real = l.within && l.within.total ? l.within.done / l.within.total : 0;
12942
+ const real = l.withinFrac ?? (l.within && l.within.total ? l.within.done / l.within.total : 0);
12943
12943
  const creep = l.i >= 1 ? 0.9 * (1 - Math.exp(-(Date.now() - m.at) / 9e4)) : 0;
12944
- const w = Math.min(1, Math.max(real, creep));
12944
+ const w = Math.min(Math.max(0, l.total - (l.i - 1)), Math.max(real, creep));
12945
12945
  m.shown = Math.max(m.shown, Math.min(1, Math.max(0, (l.i - 1 + w) / l.total)));
12946
12946
  laneMemo.set(name, m);
12947
12947
  return m.shown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polymath-society",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
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",