polymath-society 0.2.9 → 0.2.11

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
@@ -2117,7 +2117,8 @@ var init_plan = __esm({
2117
2117
  // src/wizard.ts
2118
2118
  var wizard_exports = {};
2119
2119
  __export(wizard_exports, {
2120
- runWizard: () => runWizard
2120
+ runWizard: () => runWizard,
2121
+ sayExportLinks: () => sayExportLinks
2121
2122
  });
2122
2123
  import { promises as fs35 } from "fs";
2123
2124
  import os10 from "os";
@@ -2152,6 +2153,24 @@ async function scanJsonlDir(label, dir) {
2152
2153
  await walk(dir, 0);
2153
2154
  return { label, dir, files, newest: newest ? new Date(newest).toISOString().slice(0, 10) : null };
2154
2155
  }
2156
+ async function sayExportLinks(say, haveKinds) {
2157
+ const { EXPORT_LINKS: EXPORT_LINKS2, osc8: osc82 } = await Promise.resolve().then(() => (init_exportLinks(), exportLinks_exports));
2158
+ const requestable = EXPORT_LINKS2.filter((l) => l.requestUrl);
2159
+ if (!requestable.length) return;
2160
+ const missing = requestable.filter((l) => !haveKinds.has(l.kind));
2161
+ say();
2162
+ say(` ${bold("Export more of your history")} \u2014 one click each:`);
2163
+ if (missing.length) {
2164
+ say(` ${missing.map((l) => l.label).join(", ")} ${missing.length === 1 ? "is" : "are"} not on this machine yet.`);
2165
+ }
2166
+ for (const l of requestable) {
2167
+ const have = haveKinds.has(l.kind);
2168
+ say(` ${bold(l.label)}${have ? green(" \u2713 already have one") : ""} ${osc82(l.requestUrl, l.requestUrl)}`);
2169
+ say(` ${l.buttonPath} \xB7 ${l.eta}`);
2170
+ }
2171
+ say(` ${dim("Run polymath-society again once a download lands \u2014 it finds the zip on its own.")}`);
2172
+ say();
2173
+ }
2155
2174
  function describeExport(f, i, included, hadPrevious) {
2156
2175
  const kind = {
2157
2176
  chatgpt: "ChatGPT export",
@@ -2327,20 +2346,7 @@ async function runWizard() {
2327
2346
  const defaults = found.map(() => true);
2328
2347
  say(` Found ${dim("(identified locally from the files themselves)")}:`);
2329
2348
  found.forEach((f, i) => say(describeExport(f, i, defaults[i], hadPrevious)));
2330
- {
2331
- const { EXPORT_LINKS: EXPORT_LINKS2, osc8: osc82 } = await Promise.resolve().then(() => (init_exportLinks(), exportLinks_exports));
2332
- const haveKinds = new Set(found.map((f) => f.kind));
2333
- const missing = EXPORT_LINKS2.filter((l) => l.requestUrl && !haveKinds.has(l.kind));
2334
- if (missing.length) {
2335
- say();
2336
- say(` ${bold("Not on this machine yet")} \u2014 one click to request each:`);
2337
- for (const l of missing) {
2338
- say(` ${bold(l.label)} ${osc82(l.requestUrl, l.requestUrl)}`);
2339
- say(` ${l.buttonPath} \xB7 ${l.eta}`);
2340
- }
2341
- say();
2342
- }
2343
- }
2349
+ await sayExportLinks(say, new Set(found.map((f) => f.kind)));
2344
2350
  const include = await pick2(defaults, hadPrevious);
2345
2351
  const manifest = {
2346
2352
  approvedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2360,16 +2366,7 @@ async function runWizard() {
2360
2366
  }
2361
2367
  } else {
2362
2368
  await writeManifest(dataDir, { approvedAt: (/* @__PURE__ */ new Date()).toISOString(), agents, exports: { included: [], excluded: [] } });
2363
- {
2364
- const { EXPORT_LINKS: EXPORT_LINKS2, osc8: osc82 } = await Promise.resolve().then(() => (init_exportLinks(), exportLinks_exports));
2365
- const missing = EXPORT_LINKS2.filter((l) => l.requestUrl);
2366
- say();
2367
- say(` ${bold("You can request these exports")} \u2014 one click each:`);
2368
- for (const l of missing) {
2369
- say(` ${bold(l.label)} ${osc82(l.requestUrl, l.requestUrl)}`);
2370
- say(` ${l.buttonPath} \xB7 ${l.eta}`);
2371
- }
2372
- }
2369
+ await sayExportLinks(say, /* @__PURE__ */ new Set());
2373
2370
  }
2374
2371
  {
2375
2372
  const { EXPORT_LINKS: EXPORT_LINKS2 } = await Promise.resolve().then(() => (init_exportLinks(), exportLinks_exports));
@@ -24866,7 +24863,7 @@ function apportionHeavy(budget) {
24866
24863
  const grade = Math.max(2, Math.floor(budget * 0.45));
24867
24864
  const digest = Math.max(1, Math.floor(budget * 0.17));
24868
24865
  const walkthrough = Math.max(1, Math.floor(budget * 0.17));
24869
- const delegation = Math.max(1, budget - grade - digest - walkthrough - 1);
24866
+ const delegation = Math.max(1, budget - grade - digest - walkthrough - 2);
24870
24867
  return { grade, digest, walkthrough, delegation };
24871
24868
  }
24872
24869
  function stages(o) {
@@ -24899,6 +24896,14 @@ function stages(o) {
24899
24896
  { script: "coding-walkthrough.mts", label: "Per-session walkthroughs", llm: true, args: ["--model", model, "--conc", String(h.walkthrough)], batch: "heavy" },
24900
24897
  { script: "coding-frontier-detail.mts", label: "What you're doing at the frontier", llm: true, args: [], out: "coding/frontier-detail.json", batch: "heavy" },
24901
24898
  { script: "coding-delegation.mts", label: "Delegation patterns", llm: true, args: ["--model", model, "--conc", String(h.delegation)], out: "coding/delegation-rollup.json", batch: "heavy" },
24899
+ // The FULL focus stage: flow-rule judgment + the paste-into-CLAUDE.md block.
24900
+ // The deterministic --skip-llm run above keeps the numbers current every
24901
+ // run; this one produces the AI block — it was MISSING from the graded tier
24902
+ // entirely (688d788 added focus for the numbers-only tier and no one added
24903
+ // the companion), so overnight full runs shipped a report with no flow
24904
+ // rules and nothing failed loud (found 2026-07-15). Both LLM passes are
24905
+ // prompt-hash gated, so an unchanged corpus re-run costs zero calls.
24906
+ { script: "coding-focus.mts", label: "Flow rules + focus judgment", llm: true, args: [], out: "coding/focus.json", batch: "heavy" },
24902
24907
  // ---- corpus aggregate (reads grades; deterministic rollup) ----
24903
24908
  { script: "coding-aggregate.mts", label: "Corpus aggregate \u2014 ability ceiling + flow", llm: false, args: [], out: "coding/aggregate.json" },
24904
24909
  // ---- grade readers (grades/* is complete once aggregate ran) ----
@@ -25256,7 +25261,19 @@ async function runChatPipeline(o) {
25256
25261
  }
25257
25262
  log.event("ingest-export", { ok, ms: Date.now() - stStart, source: f.kind });
25258
25263
  }
25264
+ const minNewDocs = Number(process.env.POLYMATH_MIN_NEW_DOCS ?? 10);
25265
+ const signalDocs = async () => {
25266
+ const t = await fs32.readFile(path37.join(dataRoot, "signals", "index.jsonl"), "utf8").catch(() => "");
25267
+ return t ? t.split("\n").filter(Boolean).length : 0;
25268
+ };
25269
+ const docsBeforeTagger = await signalDocs();
25270
+ let upToDate = false;
25259
25271
  for (const st of chatEngineStages()) {
25272
+ if (upToDate && st.llm) {
25273
+ o.onStage?.(++idx, total, `${st.label} \u2014 up to date, skipped`, st.llm);
25274
+ log.event(st.script.replace(/\.mts$/, ""), { ok: true, ms: 0, skipped: true });
25275
+ continue;
25276
+ }
25260
25277
  o.onStage?.(++idx, total, st.label, st.llm);
25261
25278
  const stStart = Date.now();
25262
25279
  let ok = true;
@@ -25270,6 +25287,14 @@ async function runChatPipeline(o) {
25270
25287
  }
25271
25288
  const sha = st.out ? await fileSha(path37.join(dataRoot, st.out)) : null;
25272
25289
  log.event(st.script.replace(/\.mts$/, ""), { ok, ms: Date.now() - stStart, ...sha ? { sha } : {} });
25290
+ if (st.script === "run-tagger.mts" && ok && minNewDocs > 0) {
25291
+ const newDocs = await signalDocs() - docsBeforeTagger;
25292
+ const report = await fs32.readFile(path37.join(dataRoot, "engine-pilot", "public-report.json"), "utf8").then((s) => JSON.parse(s)).catch(() => null);
25293
+ if (report?.headline && (report.spikes?.length ?? 0) > 0 && newDocs < minNewDocs) {
25294
+ upToDate = true;
25295
+ 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.`);
25296
+ }
25297
+ }
25273
25298
  }
25274
25299
  log.event("pipeline_completed", { ok: failed.length === 0, failedStages: failed.length, ms: Date.now() - t0 });
25275
25300
  await log.finish();
@@ -16902,6 +16902,14 @@ async function run2() {
16902
16902
  }
16903
16903
  }
16904
16904
  if (!sigs.length) throw new Error("loops: empty signals index \u2014 refusing to write");
16905
+ const fingerprint = `${sigs.length}:${sigs.reduce((a, s) => s.date > a ? s.date : a, "")}`;
16906
+ if (!process.env.LOOPS_REFRESH) {
16907
+ const prev2 = await fs6.readFile(OUT2, "utf8").then(JSON.parse).catch(() => null);
16908
+ if (prev2?.inputFingerprint === fingerprint && prev2?.loops?.loops?.length) {
16909
+ console.log(`loops: inputs unchanged (${sigs.length} signals) \u2014 keeping ${prev2.generatedAt} (LOOPS_REFRESH=1 to redo)`);
16910
+ return;
16911
+ }
16912
+ }
16905
16913
  const artifacts = importArtifactDates(sigs);
16906
16914
  const dated = sigs.filter((s) => !artifacts.has(s.date));
16907
16915
  const chats = dated.filter((s) => s.source === "chatgpt" || s.source === "claude");
@@ -16988,6 +16996,8 @@ ${sample.join("\n")}`;
16988
16996
  rules.sort((a, b) => a.date.localeCompare(b.date));
16989
16997
  const out = {
16990
16998
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
16999
+ inputFingerprint: fingerprint,
17000
+ // the skip gate above compares against this
16991
17001
  totals: { signals: sigs.length, dated: dated.length, chats: chats.length, ruleDeclarations: rules.length, importDatesExcluded: [...artifacts] },
16992
17002
  chatsByMonth: monthCounts(chats.map((c) => c.date)),
16993
17003
  rules,
@@ -135,6 +135,19 @@ var require_secure_json_parse = __commonJS({
135
135
 
136
136
  // ../../scripts/coding-focus.mts
137
137
  import { promises as fs6 } from "fs";
138
+
139
+ // ../../lib/agents/coding/promptCache.ts
140
+ import { createHash } from "crypto";
141
+ function promptSha(parts) {
142
+ const h = createHash("sha256");
143
+ for (const p of parts) {
144
+ h.update(p ?? "");
145
+ h.update("\0");
146
+ }
147
+ return h.digest("hex").slice(0, 16);
148
+ }
149
+
150
+ // ../../scripts/coding-focus.mts
138
151
  import path10 from "path";
139
152
 
140
153
  // ../../lib/agents/shared/agent.ts
@@ -16170,7 +16183,13 @@ async function run2() {
16170
16183
 
16171
16184
  SESSIONS:
16172
16185
  ${JSON.stringify(listing, null, 1)}`;
16173
- try {
16186
+ const clusterSha = promptSha([cprompt]);
16187
+ if (prev?.features?.length && prev?.featuresPromptSha === clusterSha && !process.env.FOCUS_REFRESH) {
16188
+ out.features = prev.features;
16189
+ out.featuresDaysUnit = prev.featuresDaysUnit ?? "min";
16190
+ out.featuresPromptSha = clusterSha;
16191
+ console.log("focus: cluster inputs unchanged \u2014 reusing features (FOCUS_REFRESH=1 to redo)");
16192
+ } else try {
16174
16193
  const cr = await invokeAgent({ prompt: cprompt, systemPrompt: "You produce strict JSON only.", maxTurns: 3, timeoutMs: 48e4, model: "sonnet" });
16175
16194
  let cj = cr.json ?? null;
16176
16195
  if (!cj) cj = parseModelJson(cr.raw);
@@ -16205,6 +16224,7 @@ ${JSON.stringify(listing, null, 1)}`;
16205
16224
  }
16206
16225
  out.features = feats.sort((a, b) => Object.keys(b.days).length - Object.keys(a.days).length);
16207
16226
  out.featuresDaysUnit = "min";
16227
+ out.featuresPromptSha = clusterSha;
16208
16228
  console.log(`focus: ${out.features.length} semantic features from ${recentSess.length} sessions (${stray.length} unmapped \u2192 Everything else)`);
16209
16229
  } else console.error(`focus: feature clustering returned no usable JSON (raw ${String(cr.raw || "").length} chars)`);
16210
16230
  } catch (e) {
@@ -16229,7 +16249,11 @@ ${examples}
16229
16249
  ${summary}
16230
16250
 
16231
16251
  Return the Part 5 JSON only.`;
16232
- try {
16252
+ const judgeSha = promptSha([prompt, process.env.FOCUS_JUDGE_MODEL || "opus"]);
16253
+ if (prev?.llm?.promptSha === judgeSha && !process.env.FOCUS_REFRESH) {
16254
+ out.llm = prev.llm;
16255
+ console.log("focus: judgment inputs unchanged \u2014 reusing llm block (FOCUS_REFRESH=1 to redo)");
16256
+ } else try {
16233
16257
  const judgeModel = process.env.FOCUS_JUDGE_MODEL || "opus";
16234
16258
  const run22 = await invokeAgent({ prompt, systemPrompt: "You produce strict JSON per the rubric's output contract. No prose outside the JSON.", maxTurns: 3, timeoutMs: 48e4, model: judgeModel });
16235
16259
  let parsed = run22.json ?? null;
@@ -16242,7 +16266,7 @@ Return the Part 5 JSON only.`;
16242
16266
  The block to append in step 1:
16243
16267
  ${parsed.claudeMdSnippet}`;
16244
16268
  }
16245
- if (parsed && (parsed.causalFindings?.length || parsed.flowKinds)) out.llm = { ...parsed, model: judgeModel, generatedAt: (/* @__PURE__ */ new Date()).toISOString() };
16269
+ if (parsed && (parsed.causalFindings?.length || parsed.flowKinds)) out.llm = { ...parsed, model: judgeModel, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), promptSha: judgeSha };
16246
16270
  else console.error(`focus: LLM pass returned no usable JSON (raw ${String(run22.raw || "").length} chars) \u2014 deterministic data written, causal block empty`);
16247
16271
  } catch (e) {
16248
16272
  console.error("focus: LLM pass failed \u2014", e.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polymath-society",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
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",