polymath-society 0.2.10 → 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
@@ -24863,7 +24863,7 @@ function apportionHeavy(budget) {
24863
24863
  const grade = Math.max(2, Math.floor(budget * 0.45));
24864
24864
  const digest = Math.max(1, Math.floor(budget * 0.17));
24865
24865
  const walkthrough = Math.max(1, Math.floor(budget * 0.17));
24866
- const delegation = Math.max(1, budget - grade - digest - walkthrough - 1);
24866
+ const delegation = Math.max(1, budget - grade - digest - walkthrough - 2);
24867
24867
  return { grade, digest, walkthrough, delegation };
24868
24868
  }
24869
24869
  function stages(o) {
@@ -24896,6 +24896,14 @@ function stages(o) {
24896
24896
  { script: "coding-walkthrough.mts", label: "Per-session walkthroughs", llm: true, args: ["--model", model, "--conc", String(h.walkthrough)], batch: "heavy" },
24897
24897
  { script: "coding-frontier-detail.mts", label: "What you're doing at the frontier", llm: true, args: [], out: "coding/frontier-detail.json", batch: "heavy" },
24898
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" },
24899
24907
  // ---- corpus aggregate (reads grades; deterministic rollup) ----
24900
24908
  { script: "coding-aggregate.mts", label: "Corpus aggregate \u2014 ability ceiling + flow", llm: false, args: [], out: "coding/aggregate.json" },
24901
24909
  // ---- grade readers (grades/* is complete once aggregate ran) ----
@@ -25253,7 +25261,19 @@ async function runChatPipeline(o) {
25253
25261
  }
25254
25262
  log.event("ingest-export", { ok, ms: Date.now() - stStart, source: f.kind });
25255
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;
25256
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
+ }
25257
25277
  o.onStage?.(++idx, total, st.label, st.llm);
25258
25278
  const stStart = Date.now();
25259
25279
  let ok = true;
@@ -25267,6 +25287,14 @@ async function runChatPipeline(o) {
25267
25287
  }
25268
25288
  const sha = st.out ? await fileSha(path37.join(dataRoot, st.out)) : null;
25269
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
+ }
25270
25298
  }
25271
25299
  log.event("pipeline_completed", { ok: failed.length === 0, failedStages: failed.length, ms: Date.now() - t0 });
25272
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.10",
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",