Contributing
How to set up a development environment, the conventions the codebase follows, and how changes are verified. If you're new to the internals, read the How it works first.
Development setup
git clone https://github.com/chizhangucb/chronicle.git
cd chronicle
npm install
npm run dev # Vite dev server + API in one process → http://localhost:4173npm run dev is the fastest loop: the Express API is mounted inside the Vite dev server, so both the React UI and the server modules hot-reload in one process on one port.
Other useful commands:
npm run build # vite build → dist/ (the static client bundle)
npm run standalone # production build + headless server (UI + /api) → http://localhost:41730
npm run typecheck # tsc -b — MUST exit 0 (see the TypeScript section below)
npm test # node --test 'test/**/*.test.mjs'Chronicle writes all of its data under ~/.chronicle/ (override with CHRONICLE_DATA_DIR). Nothing you do in development touches your source logs or project repositories — Chronicle is strictly read-only on foreign data. See Supported tools & configuration for the full directory layout and environment variables.
TypeScript: the no-compile-step rule
Chronicle is mid-migration to .ts/.tsx, and it's deliberately built around not having a build step for the server. Node 24 strips TypeScript types at load time, so the server imports and runs .ts files directly (import './db.ts', not ./db.js) — tsc here is a type checker only, never a compiler for dev/local runs. This has real constraints:
npm run typecheck(tsc -b) must exit 0. That's the whole gate — there's no separate build to catch type errors.- Erasable-syntax-only. Node's strip-only loader rejects
enum,namespace, and parameter properties (constructor(private x)) at runtime. Useas constunions instead of enums; assign fields in the constructor body. - Explicit
.ts/.tsximport extensions, relative imports only. Once a file is.ts, every importer's specifier must point at.ts/.tsx— Node does not rewrite./x.jsto./x.ts. - Full
strict: true. Don't weaken strict mode or silence errors withany/@ts-ignore— type the real shape. shared/types.ts(repo root) is the cross-boundary contract — the normalized event model (Kind,Event,Session, …); the token cell and the one parse of a session's usage JSON live beside it inshared/usage.ts(UsageCell,parseUsage,addCell), and the row and result shapes every route answers with live inshared/rows.ts,shared/results.tsandshared/explore.ts. Both sides importshared/by relative path — one convention, no alias.
The one place a real build happens is publishing: npm run prepack compiles the server to plain JS (dist-server/) so the published npm package doesn't require Node to strip types from node_modules code. Local dev never touches that path.
Conventions
- Keep new endpoints in the existing Express app, mounted route files under
server/routes/. Add amount*function there and call it fromserver/api.tsso it's registered once and works identically in dev andnpm run standalone. - Plain React + one
styles.css. There is no UI framework and no chart library beyond Recharts wrapped insrc/charts/ChartWrapper.tsxfor the newer views — match the existing style rather than introducing a new one. - Everything heavy is heuristic and local. Redaction, cost accounting, and Insights aggregation run entirely on-device with no LLM calls. Preserve that offline guarantee — never add a network dependency to a core feature.
- Read-only on foreign systems. SQLite sources are copied to a temp location (including their
-wal/-shmfiles) before opening; original logs and repos are never written. - Long-lived state lives on
globalThis(e.g. auto-sync's watchers/timers) so Vite's SSR module reloads don't orphan watchers or child processes. - Single source of truth for shared vocabulary. Chat-type labels live only in
src/kinds.ts; per-model context windows and prices live only insrc/models.ts. Add new wording or numbers there, never inline. - Destructive or user-visible operations back up first (under
~/.chronicle/backups/). Deleting a session or project tombstones it rather than silently dropping it, so a later sync can't resurrect it by accident; redaction is one-way.
Branch and PR workflow
Use a branch and a pull request for any non-trivial change — a fix/… or feat/… branch, pushed, with gh pr create, even when working solo. Reserve direct commits to main for trivial, agreed one-offs. After a PR merges, return your local checkout to main:
git checkout main && git pull && git fetch --prune && git branch -D <branch>The project-card Git pill in the UI reads the checkout's live branch on every /api/projects call (no caching), so if it shows a feature branch after a merge, the checkout is still on that branch — switch back to main.
Verifying changes
There is no full end-to-end test runner wired up beyond npm test (parser unit tests against fixtures in test/fixtures/). Beyond that, features are verified end-to-end against real data. The fastest end-to-end check is to import Chronicle's own Claude Code session and click around — time-travel, playback, and Insights all work on Chronicle's own construction history.
When you add a new source tool, follow the walkthrough in How it works and validate it against a fixture plus a real session before opening a PR.
Where things live
The Architecture page maps the codebase in detail. In short:
server/ Express API (server/routes/) + parsers + Git engine + insights/explore/content
engines + live streaming + security + auto-sync — runs .ts directly, no build step
src/ React UI (Vite) — plain React + one styles.css
bin/ chronicle.mjs — the npx/CLI launcher
shared/ types.ts — the normalized event model shared by server and client
docs/ This documentation setGoing deeper
This page is the setup and the day-to-day loop. The rest of the contributor knowledge is split by what you need:
- Architecture: the shape to keep, the load-bearing decisions, and where the seams are.
- Code map: what lives where, and which file owns a decision.
- Patterns: the TypeScript rules and the verification loop.
- Gotchas: the traps that have already cost someone a day.
- Standards: what a PR has to clear before it lands.
- Voice: how Chronicle writes, in docs, comments and PRs.
- Releasing: how a tag becomes an npm publish.
Related
- How it works — the system design, data model, ingestion, and every API route.