Glossary
Indexed at commit
51ce1a4on 2026-08-26 · view on GitHub
Domain terms used throughout cc-analyzer and this wiki, grounded in the code that implements them.
Relevant source files
Terms
Session — One Claude Code conversation, stored as a single JSONL file at <claude-root>/projects/<project>/<session>.jsonl (~/.claude by default). Its basename is usually a UUID and serves as its id.
Project — A directory under a Claude root's projects/ grouping the sessions for one working directory. Its stable project id is its encoded directory name prefixed with its root's slug (<slug>~<name>) — uniformly, for every root, so an id is a fact about a directory rather than about which root currently sorts first, and two roots holding the same working directory never merge in an aggregate. Raw ids are storage identity only: projectDisplayName() renders one for a person, and resolveProjectRef() accepts a bare name back, reporting ambiguity rather than guessing. The authoritative human path comes from a session's cwd field, not from decoding the id.
Event / SessionEvent — One parsed line of a session file. Events are typed (user, assistant, and others) and validated with Zod; an unrecognized or drifted line becomes a tolerant "unknown" event rather than an error.
Turn — The central unit of analysis: one genuine user prompt plus every assistant API call and tool loop until the next genuine prompt. Turn boundaries are set by isRealPrompt() (src/core/analyze.ts:L1-L60).
Real prompt — A user event that starts a new turn: not isMeta, and carrying content other than tool_result blocks. Tool-result-only user events are loop continuations, not turns.
Step — A fine-grained item within a turn (prompt, thinking, assistant text, an individual tool call and its result), produced by steps.ts for the per-turn timeline shown in the TUI session detail and the web per-turn view (src/core/steps.ts:L1-L60).
API call — One assistant event: a single request to the model, carrying a usage block, a model id, and any tool-use blocks it produced. A turn aggregates one or more.
Tool call — A tool_use block emitted by the assistant (Bash, Edit, Read, …), with its error status resolved by matching against the corresponding tool_result.
Streaming analysis — A consumer API that folds a session's events into metrics without holding the entire event array in memory, for very large sessions; it complements the in-memory analyzeSession() (src/core/analyze.ts:L1-L40).
Sidechain — An API call made outside the main conversation thread (marked isSidechain), typically from subagent work.
Sidechain burst — One contiguous run of sidechain API calls: a single subagent's work, from its first call to its last. Carries the turnIndex open when the burst began, its own call count, cost, and token counts, and a subagentType whose reliability depends on the layout (see Session tree). From the per-session subagents/ layout the burst also carries the subagent's agentId and spawnDepth, and its type is read from that agent's .meta.json — exact, with nested agents forming their own bursts. From the older inline layout the type is best-effort, matched back to the dispatching Task tool call and absent when it cannot be attributed. Exactly-attributed bursts are excluded from the prompt-matching pass and its counts, so a session holding both layouts cannot have exact types overwritten by guesses. groupSidechainBursts() folds bursts into one row per subagent type ranked by cost — with unattributable bursts collected under (unmatched) rather than dropped — and burstAttributionNote() decides which caveat a render site prints, since which one is true is a property of the data (src/core/analyze.ts, src/core/chart-series.ts).
Orphaned session — A <sessionId>/subagents/ directory holding at least one transcript whose <sessionId>.jsonl no longer exists: the main transcript was deleted and the subagent work beside it was left behind. That work was billed, so it is discovered and indexed rather than dropped — as a row that is entirely sidechain, with no turns or prompts, since no main chain survives. Its path remains the absent parent's, keeping the row key stable so a restored .jsonl re-attaches instead of forking a second row. Distinguished from a genuinely stale index row (nothing left on disk at all), which the web session route still 404s (src/core/discover.ts).
Session tree — A session's files: its parent transcript plus the subagent transcripts Claude Code writes to <projectDir>/<sessionId>/subagents/agent-<agentId>.jsonl (each with a sibling .meta.json carrying {agentType, spawnDepth}). Those events carry the parent's sessionId and isSidechain: true, so merging them into the parent's stream makes every sidechain metric work unchanged. sessionTree() names the two roles ({ parent?, subagents }) rather than ordering them positionally — they are read differently (an unreadable parent throws, an unreadable subagent degrades) and an orphan has no parent at all — and streamSessionTree/parseSessionTree merge the files by timestamp — not by concatenation, since a subagent call must land in the turn that was open when it happened. The two layouts are mutually exclusive (a session with a subagents/ directory has no inline sidechain usage), so the merge cannot double-count. A session's sizeBytes/mtimeMs fold across the whole tree so the indexer notices growth that happened only inside subagents/ (src/core/discover.ts, src/core/parser.ts).
Skill / Subagent — A named capability invoked via the Skill tool, and a delegated agent launched via the Task tool (subagent_type); both are recorded per session and surfaced in the tools analytics.
Turn-scoped skill cost — The primary skill-cost number: the total cost of the turns that invoked a skill (the turn's API calls, its tool loop, and any subagent burst inside it), accumulated per session in SessionAnalysis.skillTurnCosts, stored in the index column skill_turn_costs_json (schema v10), and summed across sessions into SkillUsageRow.attributedTurns / attributedCost. Tighter than the session-scoped totalCost (a session's whole cost charged to every skill it touched, kept as an upper bound), but still correlational, not causal: a turn invoking several skills counts its full cost toward each. Every surface prints the shared SKILL_COST_CAVEAT (src/core/analyze.ts, src/core/stats-types.ts).
Parse coverage — How much of a session file this build of the parser actually understood: lines seen, parseErrors (lines that produced no event — invalid JSON or a non-object), and unknownEvents (lines kept only as tolerant "unknown" events, whether a known type whose schema drifted or a type this parser has never seen). Carried on SessionAnalysis.parseCoverage, stored in the index columns parse_lines / parse_errors / unknown_events (schema v11), and rolled up portfolio-wide and per Claude Code version by parseCoverage() as unparsedShare = (parseErrors + unknownEvents) / lines. It exists because the JSONL format is undocumented and changes between releases: unparsed lines are silently excluded from every metric, so this is the signal that says when the numbers are incomplete — and what the parse-coverage-drop insight fires on (src/core/parser.ts, src/core/stats.ts).
Thrash — A loop that burns tokens without making progress. cc-analyzer detects two forms: the edit→test→fail loop, measured as testFailStreak — the longest run of consecutive failing test runs on one chain (a pass resets it; the edits between the failures do not; a new turn does) — and redundant reads (below). Stored in the index columns test_fail_streak / redundant_reads / reread_files_json (schema v12), surfaced by the edit-test-thrash and repeated-file-reads session diagnostics and the test-thrash-pattern and reread-heavy portfolio insight rules (src/core/analyze.ts, src/core/session-diagnostics.ts).
Correction turn — A real user prompt (per isRealPrompt) that opens with a correction marker — "no, …", "that's not what I meant", "undo that", "still broken", "try again", and similar — meaning the previous turn's work is being redone. Detected by isCorrectionPrompt in events.ts: a conservative, English-only keyword heuristic that scans only the first ~120 characters in two tiers — outcome and miscommunication phrases ("that's not what I meant", "still broken", "same error") may match anywhere in that window, while imperative and ambiguous ones ("no, …", "undo that", "go back to", "try again", "not working") must open the prompt, since each is also ordinary product language mid-sentence ("add a back button so users can go back to the list view") — and never matches slash commands or machine-looking prompts (leading <, /, or [). It undercounts by design — false positives are the failure mode to avoid — and every surface prints the shared CORRECTION_CAVEAT. Stored in the index column correction_turns (schema v13, so the phrase list is baked in until a reindex — a pinning test over the exported pattern sources fails on any edit and instructs the SCHEMA_VERSION bump), surfaced by the correction-loop session diagnostic and the correction-heavy portfolio insight rule (src/core/events.ts, src/core/analyze.ts).
Context growth attribution — Which calls filled the context window. For consecutive main-chain API calls, delta = promptTokens(N+1) − promptTokens(N) − outputTokens(N) is the payload that entered the context between them, attributed to the steps call N issued ("this Read added 47k tokens"). Built by the bun-free buildContextGrowth() in chart-series.ts. Sidechains are excluded (their own windows), a compaction between two calls skips the pair (counted in skippedAcrossCompactions), and non-positive deltas produce no entry. No dollar figure is derived: cache TTL expiry and later compactions both break the "Δtokens × remaining calls × cache-read rate" multiplication, so this is a token observation, not a bill — the exported CONTEXT_GROWTH_CAVEAT says so verbatim at every render site (src/core/chart-series.ts, src/core/stats-types.ts).
Turn cost shape — Why one turn was expensive, as a single named shape from the bun-free turnCostShape() in chart-series.ts: subagent (≥60% of the turn's cost ran on sidechains), cache-churn (≥50% cache write — the prompt prefix kept changing, so each call re-established a cache the next invalidated), generation (≥35% output), or long-context (≥50% cache read and ≥6 API calls). A pure function of TurnPoint, checked most-specific-first, returning undefined when nothing dominates rather than a "mixed" bucket. The cost-composition counterpart to turnFlags, and like it the one definition every render site shares — web Turns rows and per-turn bar tooltip, the CLI turns-table shape column, the TUI turns detail (src/core/chart-series.ts).
Interruption — The user hitting Esc on a response (or a pending tool call) mid-flight. Claude Code records it as a literal machine-written user message — [Request interrupted by user] or [Request interrupted by user for tool use] — detected verbatim by the shared isInterruptionEvent / isInterruptionMarker helpers, on the message text or inside a tool_result block's content (string or nested blocks) when the interrupt cancelled a pending tool call. A tool_result carrier is not a real prompt, so it marks the turn already open instead of opening one. Counted once per turn into SessionAnalysis.interruptionTurns (index column interruption_turns, schema v13), main chain only: a subagent's interruption belongs to its burst. The marker message is itself a real prompt, so turn segmentation is unchanged and the marker typically opens its own short turn. Independent of correction turns — the two counters deliberately do not dedupe against each other. The session-health doctor reuses the same event helper to identify a history that ended after an interruption (src/core/events.ts, src/core/analyze.ts).
Redundant read — A Read of a file beyond the second read of that same file on the same chain — the third read is the first redundant one, because a single re-read is often legitimate (the file changed after an edit). Each redundant read pays the whole file into context again. Reads with different offset/limit still count as reads of the file (a deliberate simplification — usually re-pagination of the same content); chains are isolated, and turns do not reset the count (src/core/analyze.ts).
Token categories — The four separately-priced kinds of tokens: input, output, cache-write, and cache-read (src/core/pricing.ts:L1-L60).
Cache-write (5m / 1h TTL) / Cache-read — Tokens written into the prompt cache (priced by time-to-live) and tokens served from it (priced well below input). Cache accounting is where most real spend hides (src/core/pricing.ts:L1-L60).
Price correction (PRICE_CORRECTIONS) — The one place cc-analyzer overrules its pricing source. Where a published list price and the rate Claude Code actually bills disagree, cc-analyzer follows Claude Code — the tool's purpose is to be reconciled against claude /usage, and an uncorrected rate makes every such comparison wrong by the spread with nothing on screen to explain it. Each entry is conditional on the stale value it corrects (when), so it stops applying as soon as the source catches up or the price moves again, rather than pinning a number that outlived its reason. correctPricing() applies them at the single loadPricing() boundary — covering the remote, cached, and bundled paths alike — after the cache write, so the cache stores what the source published and corrections are re-derived on read; it is pure and idempotent. The standing entry is claude-sonnet-5, published by LiteLLM at its introductory rate (through 2026-08-31) while Claude Code bills the standard $3/$15, a 1.5× spread in every token category (src/core/pricing.ts).
Long-context tier (above200k) — Anthropic's long-context (1M-context beta) rates, applied when a single API call's prompt-side tokens (input + cache-read + both cache-write categories, via promptTokens()) exceed LONG_CONTEXT_THRESHOLD (200,000). effectivePricing() swaps in the model's whole above200k rate set for that call — a whole-request switch, decided per call and never on an aggregate, matching how Anthropic actually bills the tier. Populated from LiteLLM's *_above_200k_tokens fields, falling back to Anthropic's published long-context multipliers for any field LiteLLM omits; a model with no long-context entry is never tiered (src/core/pricing.ts, src/core/pricing-source.ts).
Context tax — The tokens a session pays before the user types anything: system prompt + CLAUDE.md + MCP tool schemas, approximated by the prompt-side tokens of the session's first main-chain API call (SessionAnalysis.firstPromptTokens, index column first_prompt_tokens, schema v9). contextTax() reports it per project as median / p90 / average. A heuristic baseline — continuation sessions and large opening pastes inflate individual sessions, so the median is the honest read (src/core/stats.ts:L1-L60).
Headroom projection — How much room is left in the context window at the current pace: projectHeadroom() measures net context growth per main-chain API call across the open segment (everything since the last compaction, since a compaction resets the window) and divides the remaining window by it, yielding perCallTokens and callsToLimit. Undefined unless the model's window is known, the segment has at least three calls, and growth is positive — a shrinking or flat segment has no meaningful limit. Explicitly a projection, not a promise: one large paste or subagent digest invalidates the pace it extrapolates from (src/core/chart-series.ts).
What-if repricing — whatIfRepricing(), which replays each model's actual token mix (all four categories, both cache-write TTLs) at the rates of the other models the user ran — falling back to a canonical model per family (FALLBACK_WHATIF_MODELS) when fewer than two of theirs are priceable. Strictly a rate comparison: a different model would produce different tokens, and quality is not priced in. The same fold serves one session through sessionWhatIf() in the bun-free session-insights.ts, so a session's what-if and the portfolio's can never price the same mix differently (src/core/stats.ts:L1-L60, src/core/session-insights.ts).
Cost per outcome — What a session's spend bought, in observable units: cost per turn, per distinct file touched, per detected test run, and per hour of active time (idle gaps over five minutes excluded). A ratio whose denominator is zero is absent, not $0 — a session that touched no files has no per-file row rather than a misleading one. outcomeRows() applies that rule once and returns the rows in one fixed order, so the CLI, TUI, and web render an identical set. Every surface prints the shared OUTCOME_CAVEAT, because these pair spend with activity, not with value delivered (src/core/session-insights.ts).
Cost rank — Where one session's cost sits among indexed sessions, as a percentile plus the cohort size, computed by sessionCostRank() against two cohorts: the whole portfolio, and the session's own project. Both are always populated — the project cohort holds at least the session itself — so render sites decide by cohort size (is it big enough to be worth showing?) rather than by presence, falling back to the portfolio when a project has too few sessions. The percentile is the share of the cohort costing strictly less, so the joint-cheapest session reads p0 and never p100. It reads the index rather than the parsed file, so it is null for a session that has not been indexed (src/core/stats.ts, src/core/stats-types.ts).
Setup inventory — What is installed under the Claude config dir: skills (skills/<name>/SKILL.md), subagents (agents/<name>.md), plugins and the skills/agents/MCP servers they ship (a plugin's own servers come from its .mcp.json or manifest and stay on the plugin, never merged into the user-configured list), MCP servers (from settings.json and .claude.json — read both as the sibling ~/.claude.json of a default install and as <root>/.claude.json, where Claude Code keeps it when the directory was relocated — global or project-scoped), hook events, permission rule counts, and any pinned model. Produced by scanInventory() per root, and by scanInventories() across every configured root, both read-only and neither throwing (src/core/inventory.ts).
Setup audit — The cross-reference of the setup inventory against observed usage from the index, produced by the Bun-free buildSetupAudit(inventory, usage, today). It emits session-diagnostics-shaped findings: unused-mcp-server and error-prone-skill (warnings), unused-skill, unused-agent, unused-plugin, stale-skill, and missing-but-used (info). Every name question goes through one classifier, attribute(), which the findings ask loosely and the per-plugin numbers ask strictly. It also carries the per-plugin usage rollup. Surfaced by cc-analyzer audit, GET /api/audit, and the web Tools view's Setup section. Machine-local and historical: sessions may predate the current setup, and project-scoped items live outside the config dir (src/core/setup-audit.ts).
Per-plugin usage — buildPluginUsage(inventory, usage): observed usage and turn-scoped cost rolled up from a plugin's shipped skills, subagents, and MCP servers into one PluginUsageRow per plugin (used-of-shipped counts, invocations, subagent sessions, attributedTurns/attributedCost, last-used day), sorted by attributed cost then invocations. Usedness keeps the audit's loose name matching; the numbers are attributed strictly, so nothing is invented: a qualified row (toolkit:fmt) counts for the plugin it names, a bare row (fmt) only when it unambiguously belongs to one plugin (no user-installed skill of that name, exactly one plugin shipping it) — and then both name forms sum into the one plugin row. A bare name shared by two plugins counts as used for each but is summed into neither; a bare name that is also a user skill is shadowed and counts for no plugin at all. Cost is the same turn-scoped attribution as the skills table, so SKILL_COST_CAVEAT applies; subagent sessions are an upper bound. Rides on SetupAudit.plugins (src/core/setup-audit.ts).
Portfolio insights — The ranked, explainable findings the Bun-free rules engine buildPortfolioDiagnostics(signals) folds out of every portfolio signal (cache, compactions, context tax, what-if repricing, retries, thrash, weekly error trend, spend concentration, pricing confidence, the setup audit, subagent balance). Findings follow the session-diagnostics shape — code, severity, evidence, action, plus a project pointer when scoped — with warnings ranked before infos and dollar-backed findings first within a severity. Deliberately named heuristics with documented thresholds, not a score. Surfaced by cc-analyzer insights, the diagnostics field of GET /api/insights, and the web/TUI Insights views; signals are assembled identically everywhere by assemblePortfolioSignals(db, pricing) (src/core/portfolio-diagnostics.ts).
Weekly digest — One period of usage with deltas against the equally long period before it, plus a current-state insight snapshot: buildWeeklyDigest(db, pricing, opts) assembles it from the index, and the Bun-free buildDigestMarkdown(digest) renders it as paste-ready markdown. The default period is the last complete ISO week (Monday–Sunday) relative to today — a half-finished week would always read as a decline — and --week / ?week= selects the week containing any given day. Period metrics are session-day-scoped: a session counts wholly toward the period containing its start day (the day column), so one that ran past midnight is not split. The embedded insights are not period-scoped — they are buildPortfolioDiagnostics over the whole portfolio, i.e. current state. A period with zero sessions is a valid digest, not an error. Surfaced by cc-analyzer report (--md, --json), GET /api/report, and the web Dashboard's Weekly digest card (src/core/digest.ts, src/core/digest-signals.ts).
Cache efficiency — How well cache-write spend is amortized by later cache reads. The Insights view ranks projects and sessions by un-amortized cache-write spend (the "leakiest" work) (src/core/stats.ts:L1-L60).
Estimated cost — A cost flagged approximate because the model matched only by family heuristic (not an exact table entry) or could not be priced. Exempt: a call with zero tokens in every category (e.g. Claude Code's <synthetic> error stubs) is never flagged, since a $0 result is exact under any pricing table.
Cost basis — A persisted display preference ("api" or "subscription", default "api"), stored in <stateDir>/prefs.json (src/core/prefs.ts). Set it with cc-analyzer cost-basis on the CLI, or with the toggle on the web Dashboard hero (PUT /api/prefs) — both write through the same setCostBasis(). It never changes how a dollar figure is computed — costs are always tokens × the pricing table — only how it's framed: "api" reads it as a bill, "subscription" (for flat-plan Pro/Max users) frames the same number as API-equivalent value via one canonical sentence (costFramingNote() in the bun-free src/core/cost-framing.ts), rendered on the CLI stats report, the TUI portfolio lede, and the web Dashboard/Insights pages when set.
Untranscribed spend — Model calls Claude Code bills to a session but never writes into its JSONL: the 33 internal querySources (session titling, away_summary, compact, tool_use_summary_generation, auto_mode*, agent_*, hook_prompt, and more) plus credited stream retries. None produce an assistant event, so cc-analyzer can neither see nor price them, which makes a transcript-derived session cost a floor rather than a match for Claude Code's total_cost_usd — 7-9% low on long, hook-heavy, auto-mode sessions, less on short ones. It is not a pricing or token-counting difference: Claude Code's embedded rate table matches LiteLLM's and carries no long-context premium even for 1M-context models. Distinct from the index's cross-file de-duplication, which explains an indexed cost reading below a standalone one. Nothing on disk records the gap, so no correction factor is applied (see the Cost & Pricing reference).
Family heuristic — The model-resolution fallback: exact id → anthropic/-prefixed → opus/sonnet/haiku family, so newer versioned models still get a price (as an estimate).
SessionAnalysis — The central per-session data structure produced by analyzeSession(): totals, per-turn breakdowns, per-model usage, tools, skills, subagents, and files touched (src/core/analyze.ts:L1-L60).
Transcript / TranscriptItem — A linear, human-readable flattening of a session's events shared by the TUI and web readers.
Index — A disposable SQLite cache at ~/.config/cc-analyzer/index.db holding one flattened row per session; rebuildable from the JSONL files at any time (src/core/db.ts:L1-L60).
Cross-file usage de-dup — Continuation and copied session files repeat a parent file's assistant entries verbatim (same message.id), which used to double-count their tokens, cost, and API-call count in every portfolio rollup. Each de-duplicated call's identity is claimed by the first indexed file that counts it, tracked in the usage_keys table (key, path); a call another indexed file already claimed is skipped from this file's billing numbers exactly like a duplicate streamed continuation line. It covers billing numbers only — tool counts and turns are unaffected — and only indexed views: cc-analyzer analyze and the session pages still show a session's full standalone transcript cost. Deleting a file frees its claims, but a surviving row keeps its already-de-duped numbers until re-analyzed; a rebuild (cc-analyzer index --rebuild) reattributes everything (src/core/indexer.ts, src/core/db.ts).
Schema version — A schema_version stored in the index's meta table (currently v19, SCHEMA_VERSION). Bumping it invalidates and rebuilds the disposable cache — never a breaking change for users (src/core/db.ts:L86-L108).
Incremental indexing — Re-parsing only files changed by size + mtime, pruning rows for deleted files.
Analytics rollup — The single-table-scan fold (analyticsRollup) over the index's per-session JSON blobs that computes portfolio and project analytics in one pass, so every analytics surface shares the same numbers (src/core/stats.ts:L1-L60).
Chart series — Plottable time-series and distributions built by the bun-free chart-series.ts module (e.g. spend burn, compaction, hot files), imported directly by both the TUI and the web SPA so the two frontends chart identical data (src/core/chart-series.ts:L1-L60).
Bun-free module — A core module (stats-types.ts, chart-series.ts, session-diagnostics.ts, session-insights.ts, setup-audit.ts, portfolio-diagnostics.ts, cost-framing.ts, format-shared.ts, project-labels.ts, digest.ts) written without Bun-only APIs so the browser SPA can import it directly, keeping analytics logic single-sourced across frontends.
Trends — The time-series view (TUI TrendsView / web Trends): spend and usage over time with metric and granularity toggles, rendered as braille charts in the terminal and SVG charts on the web.
Tools analytics — The tool/skill/subagent usage view (TUI ToolsView / web Tools): which tools, skills, and subagents are used, how often, and at what cost (skills at both the turn and session scope).
Compaction tracking — Analytics that follow context-compaction events across a session/project, surfaced in the session and project charts (src/core/stats.ts:L1-L60).
Portfolio analytics — Aggregations over the whole index (spend by month/project/model, most expensive sessions, insights, trends) powering the stats command and the dashboards.
State dir — cc-analyzer's own writable directory (~/.config/cc-analyzer/, overridable via CC_ANALYZER_STATE_DIR) holding the index, pricing cache, preferences, and update-check cache. Distinct from the read-only Claude data dirs.
Claude root — One Claude Code data directory (~/.claude by default). Several can be configured and are analyzed together as one portfolio; claudeRoots() (in claude-roots.ts) resolves them from the --claude-dir= flag, CC_ANALYZER_CLAUDE_DIR, the claudeDirs preference, CLAUDE_CONFIG_DIR, then the default, first non-empty tier winning. The first resolved root is the primary one — what claudeDir() returns and whose settings.json supplies the merged inventory's pinned model. Project ids do not depend on it: every root qualifies its ids uniformly, so reordering the list never re-keys a project.
Embedded version — The build-time version, imported from package.json and bundled by bun --compile, so the running binary reports its own version (src/core/version.ts:L1-L8).
Compiled binary — A bun build --compile standalone executable, detected via the $bunfs marker in import.meta.url; self-update only runs in this mode (src/core/update.ts:L1-L40).
Self-update — cc-analyzer update: resolve the latest release, stream-download the matching asset (with a progress line and stall timeout), verify its checksum, and atomically replace the running binary (macOS/Linux); Windows delegates to the installer (src/core/update.ts:L1-L265).
Update check — A passive, once-a-day cached "update available" notice printed after quick commands; disabled in CI, non-TTY, --json, and via CC_ANALYZER_NO_UPDATE_CHECK.
SHA256SUMS / checksum verification — A manifest of asset hashes published with each release; the installers and update verify the download against it before installing, degrading gracefully when it is absent (src/core/checksum.ts:L1-L33).
Build provenance — A signed attestation generated in the release workflow (actions/attest-build-provenance) linking each published binary to the workflow run that built it, for supply-chain traceability.
SPA embedding — Serializing the Vite-built single-file front end into a string in a disposable source copy under tmp/ (leaving the tracked src/web/spa.ts placeholder untouched), so bun build --compile bakes the whole UI into the binary.
Wiki sync — The build step that copies the canonical /wiki into the VitePress site/docs/, normalizing filenames and links; /wiki is the single source of truth for the docs site.
Sources: src/core/analyze.ts:L1-L60 src/core/stats.ts:L1-L60 src/core/chart-series.ts:L1-L60 src/core/pricing.ts:L1-L60 src/core/update.ts:L1-L265