polymath-analyzer
Proprietary — All Rights Reserved. Published for distribution only; see LICENSE. No use, copying, or redistribution without written permission.
A local app for understanding how you work from your own Claude Code and Codex agent logs — parallelism, flow-state, throughput, per-project contributions, and (opt-in) AI-graded judgment criteria.
Three things, in one package:
- Deterministic metrics — computed in-process from the JSONL on your disk. Zero LLM, zero network. Facts, not judgments. (Instant and free.)
- Local LLM grading (opt-in,
--grade) — grades abstraction, taste, delegation, expertise, and frontier tool-use by invoking the localclaude -p/codexbinary you're already logged into. No API key; it spends your own subscription. Your prompts never leave the machine. - A local web UI (
serve) — the same warm "Code tab" view the hosted Polymath app shows, served on a free port, with per-section Share.
What it reads
~/.claude/projects/**/*.jsonl— Claude Code sessions~/.codex/sessions/**/*.jsonl— Codex sessions
(Override with CLAUDE_PROJECTS_DIR / CODEX_SESSIONS_DIR env vars.)
It separates your real interactive sessions from headless agent runs,
auth-ping sessions, and machine-assembled mega-prompts (the klass /
klassReason on every parsed session is transparent and auditable), de-dupes
resume/fork copies, and then computes everything below over the canonical set.
What it computes
| Metric | What it is |
|---|---|
| sessions | per-session structural facts: duration, active time, turns, tool counts, models, tokens, modes, renames |
| parallelism | sweep-line concurrency across sessions — max concurrent, minutes spent at each level, per-day overlap, top windows |
| day-chart | per-day Gantt of every conversation's active spans (clipped to local midnight) |
| flow | every 10-minute slot classified in-flow / active-pause / away (rapid rhythm + dictation beat the gaps) |
| throughput | words you actually typed per day (pasted code stripped) → a 1–11 score, plus a proven ceiling |
| projects | GitHub-style per-project days / hours / range |
| aggregate | average throughput over substantial days + flow stats (work window, % in flow, when flow peaks) |
Install
npm install polymath-analyzer
# or run the CLI without installing:
npx coding-analyzer
CLI
coding-analyzer # human-readable summary of the deterministic profile
coding-analyzer --json # full profile JSON to stdout
coding-analyzer --out p.json # write full profile JSON to a file
coding-analyzer projects # just the projects/contributions view (JSON)
coding-analyzer flow # just the per-day flow slots + totals (JSON)
coding-analyzer aggregate # just the corpus aggregate (JSON)
coding-analyzer grade # OPT-IN: AI-grade your sessions on your OWN
# Claude Code / Codex login (no API key)
coding-analyzer serve # scrape → (optionally --grade) → open the web UI
coding-analyzer serve --grade # …including the AI-graded sections
Flags: --no-codex · --idle <min> · --tz <IANA> · --span <hours> ·
--json · --out <file> · --grade · --adapter <cli|codex> · --model <id> ·
--max-sessions <n> · --port <n> · --share-url <url> · --open
Example summary:
========== DETERMINISTIC CODING PROFILE ==========
timezone America/Los_Angeles · idle-gap 5m
sessions: 106 interactive (canonical) · 1101 agent/excluded · 19 resume-copies dropped
active work time: 147.5h tool calls: 16,017 subagents: 41 queue-ahead: 2271
PARALLELISM
max concurrent sessions: 6
THROUGHPUT
proven ceiling (≥3 days): 10/11
AVG over substantial days: 7/11 — 5960 words/day over 30/50 days
FLOW
work window: 12:30pm–6:40pm over 47 active days
in flow: 63% of active time · 86.5h total (1.8h/day)
PROJECTS
4 projects · 37 active days · 102h
The local app (serve)
coding-analyzer serve # deterministic only — instant, free
coding-analyzer serve --grade # + AI-graded sections (spends your subscription)
coding-analyzer serve --grade --open --port 8765
serve scrapes ~/.claude/projects + ~/.codex/sessions, optionally grades,
starts a tiny zero-dependency HTTP server on a free port, and prints the
clickable http://localhost:<port> URL. The page reproduces the hosted app's
Code tab — the same warm palette, cards, contributions heatmap, flow stats,
throughput scatter, and (when graded) the criterion cards with verbatim receipts.
The server exposes:
| Route | What |
|---|---|
GET / |
the bundled static UI |
GET /profile.json |
the computed profile, organized into the share-contract sections |
GET /meta.json |
{ version, shareUrl } for the share bar |
POST /share |
proxies the chosen sections to the share endpoint (server-side — no browser CORS) |
Local LLM grading (no API key)
Grading is opt-in because it spends your tokens; the deterministic metrics
stay instant and free. With --grade, the package invokes the local
claude -p (or codex) binary you're already logged into — billed to your
Claude / ChatGPT subscription, never an API key. On startup it detects whether a
backend is installed and logged in; if not, it prints the exact fix (e.g. run
claude to log in) instead of failing obscurely, and serve falls back to
deterministic-only.
coding-analyzer grade # grade + write graded.json, print means
coding-analyzer grade --adapter codex # pin the Codex backend
coding-analyzer grade --max-sessions 20 # cap the token/time budget
The five graded criteria — abstraction, taste, delegation, expertise,
frontier — are ported from the main Polymath pipeline (same rubric, 1–11 ladder
with verbatim quotes, per-conversation ceiling 10). Grading plugs into the
analyze() gradeProvider seam (see Library below).
Share — per-section selection
Every section in the UI has a checkbox; tick the ones you want and hit Share selected in the footer bar. The browser sends only the ticked keys to the local server, which forwards exactly this contract to the share endpoint:
POST <shareUrl>
Content-Type: application/json
{
"source": "coding-analyzer",
"version": "<pkg version>",
"name": "<optional user-provided name>",
"sharedAt": "<ISO8601>",
"sections": { "<key>": <that section's data> }
}
key ∈ { howYouWork, snapshot, workstyle, parallelism, lixiThroughput, topDays, abstraction, taste, delegation, expertise, frontier, operate } — only ticked
keys appear in sections. The endpoint defaults to
http://localhost:3100/api/coding-share (dev) and is overridable with
--share-url <url> or PS_SHARE_URL. TODO: set the production share URL
before shipping. The local server does the POST server-side, so the browser never
hits CORS.
How the bundled UI is produced / rebuilt
The UI sources live in ui/ (index.html, styles.css, app.js — vanilla JS,
no React/Tailwind; the warm palette tokens and Card/Chip primitives are
hand-ported to plain CSS). npm run build runs tsc then scripts/build-ui.mjs,
which esbuild-bundles ui/app.js → dist/ui/app.js (minified, IIFE, zero
runtime deps) and copies the HTML/CSS. The server serves dist/ui/, which ships
inside the published package. To rebuild only the UI: npm run build:ui.
Library
import { analyze } from "polymath-analyzer";
const profile = await analyze({
codex: true, // include ~/.codex/sessions (default)
idleGapMin: 5, // gap that breaks a session into active intervals
tz: "America/Los_Angeles",
spanHourMin: 4, // "substantial day" threshold for the throughput avg
});
console.log(profile.concurrency.maxConcurrency);
console.log(profile.throughput.ceiling); // 1–11, proven on ≥3 days
console.log(profile.flow.totals.flowFraction);
console.log(profile.projects.projects);
Every building block is exported too, so you can compose your own pipeline:
import {
readAllSessions, toSessionRecords, buildConcurrency, buildDayChart,
markDuplicates, computeFlow, computeProjects, computeAggregate,
proseWords, throughputScore, throughputCeiling,
} from "polymath-analyzer";
const raw = await readAllSessions();
const interactive = raw.filter((s) => s.klass === "interactive");
const records = toSessionRecords(interactive);
await markDuplicates(records);
const canon = records.filter((r) => !r.duplicateOf);
const conc = buildConcurrency(interactive, canon);
const flow = await computeFlow(canon);
// …
Local LLM grading via analyze()'s gradeProvider
analyze() is deterministic by default and never calls a model. The grading
seam is the optional gradeProvider: pass one and analyze() attaches a
graded block of per-criterion means. The package ships LocalGradeProvider,
which implements that seam by grading on your local Claude Code / Codex CLI:
import { analyze, LocalGradeProvider } from "polymath-analyzer";
const provider = new LocalGradeProvider({
adapter: "cli", // or "codex"; omit for auto (first installed + logged-in)
model: "sonnet", // grading model (cli only; codex ignores it)
maxSessions: 20, // cap the token/time budget
});
const profile = await analyze({ gradeProvider: provider });
profile.graded; // { abstraction: { mean, n }, taste: { mean, n }, … }
provider.detail(); // richer per-criterion distributions + per-session takes (UI)
Or supply your own gradeProvider (any object with criteriaMean(records)) to
fold in grades from a different pipeline.
const profile = await analyze({
gradeProvider: { criteriaMean: () => ({ taste: { mean: 8.2, n: 40 } }) },
});
Serving programmatically
import { analyze, LocalGradeProvider, buildWebProfile, startServer } from "polymath-analyzer";
const provider = new LocalGradeProvider();
const profile = await analyze({ gradeProvider: provider });
const web = buildWebProfile(profile, provider.detail());
const { url } = await startServer({ profile: web, version: "0.2.0", shareUrl: "https://…/api/coding-share" });
console.log(url);
Project aliases
A session's project is its working directory, so the same project shows up under
several paths (parent-folder runs, old names). Pass aliases to canonicalize:
await analyze({
aliases: [
{ name: "Polymath", match: ["/polymath-society", "/polymath"], note: "merged old name" },
],
});
Without aliases, the leaf directory name is used.
Configuration
| Option (lib) | Flag (CLI) | Env | Default |
|---|---|---|---|
codex |
--no-codex |
true |
|
idleGapMin |
--idle |
5 |
|
tz |
--tz |
CODING_TZ |
America/Los_Angeles |
spanHourMin |
--span |
4 |
|
| — | --port |
free OS-assigned | |
| — | --share-url |
PS_SHARE_URL |
http://localhost:3100/api/coding-share |
| — | --adapter |
CODING_ANALYZER_ADAPTER |
auto (cli → codex) |
| — | — | CLAUDE_BIN / CODEX_BIN |
resolved from PATH |
| — | — | CLAUDE_PROJECTS_DIR |
~/.claude/projects |
| — | — | CODEX_SESSIONS_DIR |
~/.codex/sessions |
Privacy
On-device by default. The deterministic library reads local files and returns plain objects — no telemetry, no network, no model inference.
Grading (--grade) runs on the local CLI you're logged into (claude -p
/ codex exec) — your prompts are read from disk and graded on your own
subscription; nothing is sent to a third party. The only outbound network
call the package ever makes is POST /share, and only for the exact sections you
tick, to the endpoint you configure.
Requirements
Node.js ≥ 18, ESM only. Grading additionally needs Claude Code or Codex
installed and logged in (claude / codex login).
License
MIT