polymath-society 0.2.22 → 0.2.23

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
@@ -22857,6 +22857,7 @@ function apportionHeavy(budget) {
22857
22857
  function stages(o) {
22858
22858
  const model = o.model ?? "sonnet";
22859
22859
  const gradeModel = o.model ?? "haiku";
22860
+ const narrativeModel = o.model ?? "haiku";
22860
22861
  const budget = machineBudget();
22861
22862
  const h = apportionHeavy(budget);
22862
22863
  const conc = String(o.conc ?? h.grade);
@@ -22882,8 +22883,8 @@ function stages(o) {
22882
22883
  // finish inside its shadow). Budget: grade 8 + digest 3 + walkthrough 3 +
22883
22884
  // two single-call stages ≈ 16 peak subprocesses (measured-safe envelope).
22884
22885
  { script: "coding-grade.mts", label: "AI-grade every substantial session", llm: true, args: gradeArgs, batch: "heavy" },
22885
- { script: "coding-day-digest.mts", label: "Day-by-day digests", llm: true, args: ["--model", model, "--conc", String(h.digest)], out: "coding/day-digest.json", batch: "heavy" },
22886
- { script: "coding-walkthrough.mts", label: "Per-session walkthroughs", llm: true, args: ["--model", model, "--conc", String(h.walkthrough)], batch: "heavy" },
22886
+ { script: "coding-day-digest.mts", label: "Day-by-day digests", llm: true, args: ["--model", narrativeModel, "--conc", String(h.digest)], out: "coding/day-digest.json", batch: "heavy" },
22887
+ { script: "coding-walkthrough.mts", label: "Per-session walkthroughs", llm: true, args: ["--model", narrativeModel, "--conc", String(h.walkthrough)], batch: "heavy" },
22887
22888
  { script: "coding-frontier-detail.mts", label: "What you're doing at the frontier", llm: true, args: [], out: "coding/frontier-detail.json", batch: "heavy" },
22888
22889
  { script: "coding-delegation.mts", label: "Delegation patterns", llm: true, args: ["--model", model, "--conc", String(h.delegation)], out: "coding/delegation-rollup.json", batch: "heavy" },
22889
22890
  // The FULL focus stage: flow-rule judgment + the paste-into-CLAUDE.md block.
@@ -22901,7 +22902,13 @@ function stages(o) {
22901
22902
  // (one call per run; the axis verdicts are the most judgment-heavy output).
22902
22903
  // An explicit user model choice still wins.
22903
22904
  { script: "coding-agglomerate.mts", label: "Agglomerated criteria + verdicts + signature moves", llm: true, args: o.model ? ["--model", o.model] : [], out: "coding/agglomerate.json", batch: "grade-readers" },
22904
- { script: "coding-expertise.mts", label: "Domain-expertise map", llm: true, args: ["--conc", String(Math.max(2, budget - 1))], out: "coding/expertise.json", batch: "grade-readers" },
22905
+ // Domain-expertise map DISABLED (owner call 2026-07-22): the single most
22906
+ // expensive stage of the whole run ($13.88 of $49.34 measured — 129 sonnet
22907
+ // calls) and for most users the hiring-bar filter correctly returns ZERO
22908
+ // domains — $14 for an empty section. The machinery (coding-expertise.mts,
22909
+ // expertise-floor.ts) stays for the planned cheap re-enable (haiku scan or
22910
+ // fold into the grade call); the report renders nothing when the artifact
22911
+ // is absent.
22905
22912
  // ---- final synthesis (reads the finished aggregates + batch outputs) ----
22906
22913
  { script: "coding-coaching.mts", label: "Coaching + feel-seen copy", llm: true, args: [], out: "coding/coaching.json", batch: "synthesis" },
22907
22914
  { script: "coding-gap.mts", label: "Gap-to-close vs best practice", llm: true, args: [], out: "coding/gap.json", batch: "synthesis" },
@@ -24039,7 +24046,7 @@ async function detectPlan(home = os9.homedir()) {
24039
24046
  return { tier, label: LABEL[tier], windowUsd: WINDOW_USD[tier] };
24040
24047
  }
24041
24048
  function estimateLaneCosts(input) {
24042
- const codingUsd = Math.max(5, Math.min(60, Math.round(input.agentLogFiles * 17e-4)));
24049
+ const codingUsd = Math.max(5, Math.min(32, Math.round(input.agentLogFiles * 9e-4)));
24043
24050
  const chatUsd = input.chatIncluded ? Math.round(40 + input.exportTotalMB * 0.8) : 0;
24044
24051
  return { codingUsd, chatUsd };
24045
24052
  }
@@ -24074,19 +24081,16 @@ async function remainingFractions(dataRoot, agentLogFiles) {
24074
24081
  return { coding, chat: clamp(1 - chatDone, 0.1, 1) };
24075
24082
  }
24076
24083
  function scheduleLanes(plan, est) {
24077
- const dayShare = plan.tier === "max_5x" || plan.tier === "gpt_team" ? 0.2 : 0.5;
24078
- const fits = (usd) => usd <= plan.windowUsd * dayShare;
24079
- const coding = fits(est.codingUsd) ? "now" : "overnight";
24080
- const chat = est.chatUsd === 0 || fits(est.chatUsd) ? "now" : "overnight";
24084
+ const chat = est.chatUsd === 0 || est.chatUsd <= plan.windowUsd * 0.9 ? "now" : "overnight";
24081
24085
  return {
24082
- coding,
24086
+ coding: "now",
24083
24087
  chat,
24084
24088
  // No plan name here — the schedule header names the plan once, and
24085
24089
  // repeating it doubled the clumsy unknown-plan label (2026-07-22
24086
24090
  // walkthrough). Phrased for the two-column "Proposed schedule" rows:
24087
24091
  // "<lane> <verdict> — <reason>".
24088
- codingReason: coding === "now" ? `runs now \u2014 fits comfortably inside one 5-hour window` : `starts at 2:00 am tonight \u2014 a full grade would eat most of your daytime window`,
24089
- chatReason: est.chatUsd === 0 ? "not planned \u2014 no chat sources included" : chat === "now" ? `runs now, alongside coding \u2014 your chat corpus is small enough` : `starts at 2:00 am tonight \u2014 a large chat corpus; this protects your daytime quota`
24092
+ codingReason: `runs now \u2014 the report link appears right away and fills in as it goes`,
24093
+ 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`
24090
24094
  };
24091
24095
  }
24092
24096
  var UNSUPPORTED_FULL_TIERS, WINDOW_USD, LABEL;
@@ -16193,7 +16193,7 @@ ${lines.join("\n")}`);
16193
16193
  }
16194
16194
  return blocks.join("\n\n");
16195
16195
  }
16196
- var SYSTEM = "You read all the messages a person sent on ONE day across several coding-agent conversations, and say \u2014 per conversation \u2014 what they actually DID that day, semantically and specifically. Their words only; ignore mechanical scaffolding. One tight line per conversation (e.g. 'built the full prototype of the apprenticeship app', 'tuned the intelligence rubric with examples from their own read on people', 'set up an EC2 box to run the analysis overnight'). No flattery, no filler.";
16196
+ var SYSTEM = "You read all the messages a person sent on ONE day across several coding-agent conversations, and say \u2014 per conversation \u2014 what they actually DID that day, semantically and specifically. Their words only; ignore mechanical scaffolding. One tight line per conversation. HARD RULES: (1) NAME the concrete feature, bug, decision, or artifact in play ('added retry handling to the export queue', 'cut the sidebar's self-analysis tab but kept the public report') \u2014 never a category ('reviewed features', 'UI changes'). (2) A line must be so specific it could only describe THAT conversation \u2014 if it would fit another day's work, rewrite it. (3) BANNED verbs: 'worked on', 'iterated on', 'refined', 'reviewed', 'continued', 'various'. (4) Even a 1-2 message conversation gets a real read of what the person said or asked \u2014 never a meta-comment like 'repeated earlier context'. No flattery, no filler.";
16197
16197
  async function digestDay(dc, opts = {}) {
16198
16198
  const sandbox = await fs6.mkdtemp(path10.join(os3.tmpdir(), "ps-coding-day-"));
16199
16199
  try {
@@ -16205,7 +16205,7 @@ Output a SINGLE fenced \`\`\`json block:
16205
16205
  \`\`\`json
16206
16206
  { "overall": "", "conversations": [ { "sessionId": "<8-char id from a header>", "summary": "" } ] }
16207
16207
  \`\`\``;
16208
- const run2 = await invokeAgent({ prompt, systemPrompt: SYSTEM, addDir: sandbox, allowedTools: ["Read"], maxTurns: 4, timeoutMs: 3e5, model: opts.model ?? "sonnet" });
16208
+ const run2 = await invokeAgent({ prompt, systemPrompt: SYSTEM, addDir: sandbox, allowedTools: ["Read"], maxTurns: 4, timeoutMs: 3e5, model: opts.model ?? "haiku" });
16209
16209
  const raw = run2.json ?? {};
16210
16210
  const summaries = new Map((raw.conversations ?? []).map((c) => [String(c.sessionId ?? "").slice(0, 8), String(c.summary ?? "")]));
16211
16211
  return {
@@ -16570,7 +16570,7 @@ var GRADES = path11.join(CODING_DIR, "grades");
16570
16570
 
16571
16571
  // ../../scripts/coding-day-digest.mts
16572
16572
  var argv = process.argv.slice(2);
16573
- var model = argv.indexOf("--model") >= 0 ? argv[argv.indexOf("--model") + 1] : "sonnet";
16573
+ var model = argv.indexOf("--model") >= 0 ? argv[argv.indexOf("--model") + 1] : "haiku";
16574
16574
  var REDO = argv.includes("--redo");
16575
16575
  var CONC = Number(argv.indexOf("--conc") >= 0 ? argv[argv.indexOf("--conc") + 1] : "8");
16576
16576
  var CODING = CODING_DIR;
@@ -16237,7 +16237,7 @@ function buildTranscript(msgs, tz) {
16237
16237
  if (text2.length > MAXC) text2 = text2.slice(0, MAXC) + "\n\u2026[transcript truncated]";
16238
16238
  return { text: text2, turnT, userTurns: n };
16239
16239
  }
16240
- var SYSTEM = "You reconstruct, IN ORDER, what a person actually tried in one coding-agent session \u2014 the arc of attempts, realizations, dead-ends, and course-corrections, told as their story. You read the back-and-forth (YOU: = the person, AI: = the model, collapsed). You write specific, concrete phases grounded in what really happened \u2014 never generic ('iterated on the feature'). Capture the THINKING: what they tried, why it didn't satisfy them, what they realized, what they switched to. Honest about messiness and dead-ends.";
16240
+ var SYSTEM = "You reconstruct, IN ORDER, what a person actually tried in one coding-agent session \u2014 the arc of attempts, realizations, dead-ends, and course-corrections, told as their story. You read the back-and-forth (YOU: = the person, AI: = the model, collapsed). Every phase must be so specific that it could ONLY describe this session \u2014 a reader who saw ten sessions could pick this one out from the phase alone. Capture the THINKING: what they tried, why it didn't satisfy them, what they realized, what they switched to. Honest about messiness and dead-ends.";
16241
16241
  function buildPrompt(rec, transcript) {
16242
16242
  return `Below is a coding session, with the person's turns numbered [#N HH:MM].
16243
16243
 
@@ -16245,11 +16245,18 @@ SESSION: "${rec.title}"
16245
16245
 
16246
16246
  ${transcript}
16247
16247
 
16248
- Reconstruct what happened as an ORDERED list of 5\u201314 phases \u2014 the person's actual arc. Each phase:
16249
- - "what": 1\u20132 sentences, concrete and specific, in narrative voice (e.g. "Started by trying to show percentiles instead of raw scores, but nothing anchored what a percentile meant, so pivoted to comparing against big-tech engineers." / "Realized the output was AI slop \u2014 they'd only given a couple of examples, not a real spec, and asked the AI to generate more from that.").
16250
- - "startTurn": the turn number [#N] where this phase begins.
16248
+ Reconstruct what happened as an ORDERED list of phases \u2014 the person's actual arc. ONE phase per distinct move the person made: a new demand, a pushback, a scope change, a realization, a decision, a dead-end. If a turn contains two separate moves, that is two phases. Short sessions may have 5; long many-turn sessions often need 12\u201320. Never merge distinct moves to stay short.
16251
16249
 
16252
- Order phases by startTurn (ascending). Capture realizations and dead-ends, not just actions.
16250
+ Each phase:
16251
+ - "what": 1\u20132 sentences. VOICE: narrative, past tense, the person as the implied subject \u2014 start with the verb of their move ("Opened by demanding\u2026", "Pushed back when\u2026", "Caught a real gap\u2026", "Dropped the integration after\u2026"). NEVER start with "User" or "The user".
16252
+ - HARD RULES for "what":
16253
+ 1. NAME the concrete thing \u2014 the specific feature, file, bug, number, or behavior in play ("the login redirect that looped back to /home", "the CSV export that dropped its header row"), never a category ("an issue", "the feature", "some bugs").
16254
+ 2. CARRY the person's own key words when they reveal intent or feeling \u2014 short quoted fragments ('ship it as is', 'why is this still broken') are the highest-signal content.
16255
+ 3. STATE the why or the turn-of-thought when the transcript shows it: what dissatisfied them, what they realized, what they traded away. An action with no why is only half a phase.
16256
+ 4. BANNED: 'worked on', 'iterated on', 'continued', 'discussed', 'made progress', 'addressed issues', 'various fixes' \u2014 if a sentence would fit any other session, rewrite it until it wouldn't.
16257
+ - "startTurn": the turn number [#N] where this phase begins. Use the number printed in the transcript \u2014 never invent one.
16258
+
16259
+ Order phases by startTurn (ascending). Cover the WHOLE session \u2014 later turns matter as much as early ones. Capture realizations and dead-ends, not just actions.
16253
16260
 
16254
16261
  Output a SINGLE fenced \`\`\`json block:
16255
16262
  \`\`\`json
@@ -16287,7 +16294,9 @@ async function buildWalkthrough(rec, opts) {
16287
16294
  allowedTools: ["Read"],
16288
16295
  maxTurns: 6,
16289
16296
  timeoutMs: 3e5,
16290
- model: opts.model ?? "sonnet"
16297
+ // haiku default (2026-07-22): with the explicit-rules prompt above it
16298
+ // matches sonnet on real sessions at ~1/3 the cost; opts.model overrides.
16299
+ model: opts.model ?? "haiku"
16291
16300
  });
16292
16301
  const phases = validate(run2.json, turnT, times, sessionEnd);
16293
16302
  if (phases.length === 0) return null;
@@ -16679,7 +16688,7 @@ var flag = (n, d) => {
16679
16688
  const i = argv.indexOf(`--${n}`);
16680
16689
  return i >= 0 ? argv[i + 1] : d;
16681
16690
  };
16682
- var MODEL = flag("model", "sonnet");
16691
+ var MODEL = flag("model", "haiku");
16683
16692
  var CONC = Number(flag("conc", "8"));
16684
16693
  var REGEN = argv.includes("--regen");
16685
16694
  var CODING = CODING_DIR;
package/dist/web/app.js CHANGED
@@ -9886,58 +9886,6 @@ function CodingAccountabilityCard({ a }) {
9886
9886
  ] })
9887
9887
  ] });
9888
9888
  }
9889
- function Pill({ label, tone = "faint" }) {
9890
- const c = { good: "bg-accentSoft text-accent", accent: "bg-accentSoft text-accent", faint: "border border-line bg-canvas text-faint" }[tone];
9891
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: `rounded px-1.5 py-0.5 text-[11px] font-semibold ${c}`, children: label });
9892
- }
9893
- function ExpertiseMapPanel({ x, coaching }) {
9894
- const tone = (d) => d === "deep" ? "good" : d === "working" ? "accent" : "faint";
9895
- const domainLi = (dm, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("details", { className: "group", children: [
9896
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("summary", { className: "grid cursor-pointer list-none grid-cols-1 items-baseline gap-x-4 gap-y-0.5 text-[13px] [&::-webkit-details-marker]:hidden sm:grid-cols-[1.05fr_1fr]", children: [
9897
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "flex min-w-0 items-baseline gap-2", children: [
9898
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-accent", children: "\u25B8" }),
9899
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Pill, { label: dm.depth, tone: tone(dm.depth) }),
9900
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-semibold text-ink", children: dm.domain })
9901
- ] }),
9902
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[12px] leading-relaxed text-faint", children: [
9903
- dm.dateRange && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-medium", children: dm.dateRange }),
9904
- dm.whatTheyKnow && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-muted", children: [
9905
- " \u2014 ",
9906
- dm.whatTheyKnow
9907
- ] })
9908
- ] })
9909
- ] }),
9910
- dm.why && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "ml-5 mt-2 text-[12.5px] leading-relaxed text-ink", children: [
9911
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-semibold text-accent", children: "Why \u2014 " }),
9912
- dm.why
9913
- ] }),
9914
- dm.bullets?.length ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "ml-5 mt-2 space-y-1", children: dm.bullets.map((b, j) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { className: "flex gap-2 text-[12.5px] leading-relaxed text-ink", children: [
9915
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "mt-[7px] h-1 w-1 flex-none rounded-full bg-accentSoft" }),
9916
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: b })
9917
- ] }, j)) }) : dm.evidence?.slice(0, 3).map((e, j) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "ml-5 mt-2 border-l-2 border-line pl-2.5", children: [
9918
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "text-[11px] uppercase tracking-wide text-faint", children: [
9919
- e.kind,
9920
- " \xB7 ",
9921
- e.date
9922
- ] }),
9923
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-[12.5px] text-muted", children: e.knowledge })
9924
- ] }, j))
9925
- ] }) }, i);
9926
- const tech = x.domains.filter((d) => d.technical !== false);
9927
- const built = tech.filter((d) => d.kind !== "directs");
9928
- const directs = tech.filter((d) => d.kind === "directs");
9929
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Card, { className: "mb-5", children: [
9930
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-[12px] text-faint", children: "hard technical / systems domains where you have real, accumulated depth \u2014 not your eval/product work, not smart-generalist problem-solving \xB7 expand any line for the proof" }),
9931
- tech.length ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
9932
- built.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SubSection, { label: "Built & studied deeply", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "space-y-1.5", children: built.map(domainLi) }) }),
9933
- directs.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(SubSection, { label: "Directs the AI with authority", children: [
9934
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mb-2 text-[11.5px] leading-relaxed text-faint", children: "you haven\u2019t necessarily built these here, but you command the AI in them with mechanism-level authority it defers to" }),
9935
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "space-y-1.5", children: directs.map(domainLi) })
9936
- ] })
9937
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "mt-2 text-[13px] leading-relaxed text-muted", children: "No hard technical / systems domain cleared the bar \u2014 you\u2019re a very capable generalist builder, but deep specialized domains (vector DBs, distributed systems, kernels, RAG internals\u2026) aren\u2019t where your accumulated expertise lives. That\u2019s the honest picture." }),
9938
- tech.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Coaching, { text: coaching })
9939
- ] });
9940
- }
9941
9889
  var DO_THIS = {
9942
9890
  worktrees: "Replace `claude` with `claude --worktree <name>` \u2014 each session gets its own isolated checkout. That's the whole change; there's no branching workflow to adopt.",
9943
9891
  subagents: "Run `/agents` in Claude Code to save a reusable specialist (e.g. a per-criterion grader with your rubric baked in) to `.claude/agents/` \u2014 or just tell Claude \u201Cuse 4 subagents, one per criterion\u201D to fan out on the spot.",
@@ -11022,10 +10970,6 @@ function CodeAnalysisView({ data: dataProp } = {}) {
11022
10970
  data.frontierArsenal && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FrontierPanel, { f: data.frontierArsenal, detail: data.frontierDetail, coaching: data.coaching?.coaching?.frontier, gap: data.gap, parallelism: data.parallelism }),
11023
10971
  data.delegationRollup && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DelegationPanel, { r: data.delegationRollup, takes: data.criteria?.find((c) => c.key === "delegation")?.takes })
11024
10972
  ] }), ["delegation", "frontier"]),
11025
- data.expertiseMap && fs("coding:expertise", "Expertise", /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
11026
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SectionDivider, { label: "Expertise" }),
11027
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ExpertiseMapPanel, { x: data.expertiseMap, coaching: data.coaching?.coaching?.expertise })
11028
- ] }), ["expertise"]),
11029
10973
  (data.gradedSessions ?? 0) > 0 && (data.agglomeration?.verdicts || data.aggregate?.ability) && fs("coding:qualitatives", "The qualitatives", /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
11030
10974
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SectionDivider, { label: "The qualitatives", note: "Our best read, with the specific chats behind it. Coding logs are a narrow window, so thin evidence reads as a range: your real level can sit above or below what shows up here." }),
11031
10975
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AbilityPanel, { a: data.aggregate?.ability, verdicts: data.agglomeration?.verdicts, criteria: data.criteria, feelSeen: data.coaching?.feelSeen, agencyScore: (data.stackUp ?? []).find((r) => r.label === "Agency")?.score ?? null })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polymath-society",
3
- "version": "0.2.22",
3
+ "version": "0.2.23",
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",