> ## Documentation Index
> Fetch the complete documentation index at: https://reikon.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture Overview

> How Reikon's processes, IPC, and data flow fit together.

# Architecture Overview

A practical map of how Reikon is put together — written for a human reading it before
their first PR, not as a redirect to `CLAUDE.md` (which is internal guidance for Claude
Code sessions specifically, and assumes that context).

## Stack

Electron 32, electron-vite, React 18, Tailwind 3, Zustand for state, Phosphor Icons,
TypeScript throughout (no `any`), Vitest for tests.

## The three processes

Like any Electron app, Reikon runs across multiple processes with different
responsibilities:

* **Main process** (`src/main/`) — owns the window, the IPC handlers, the file watcher,
  and orchestrates analysis. Talks to disk and runs git commands; never touches the UI
  directly.
* **Renderer process** (`src/renderer/`) — the React UI. Has no direct filesystem or
  git access — everything it needs comes through IPC.
* **Preload script** (`src/preload/`) — the bridge between the two. Exposes a typed
  `window.analyzer` API via Electron's `contextBridge`; the renderer never talks to
  `ipcRenderer` directly.

Heavy analysis work (the actual repo scanning) runs in a fourth place: a **utility
process** (`src/main/worker.ts`), spawned by the main process, so a large repo's
analysis never blocks the window from responding.

## Code shared between main and renderer

Most code is environment-specific by necessity — the renderer can't touch the
filesystem, the main process has no DOM. But pure calculation logic with no
environment-specific dependencies can and should live in **`src/shared/`**, importable
from both sides (it's included in both `tsconfig.node.json` and `tsconfig.web.json`,
the same way `src/types/` already was).

The health score formula is the canonical example: it used to be three independently
hand-written copies (the desktop app, persisted snapshots, and the CLI), which had
already drifted apart in ways nobody caught until an audit found them. It's now one
implementation in `src/shared/health-score.ts`, imported by all three. If you're adding
logic that more than one of {renderer, main, CLI} needs to compute identically, this is
where it goes — not copied into each consumer "to keep in sync by hand."

## How an analysis actually runs

1. Renderer calls `window.analyzer.run(rootPath)`
2. Preload forwards it over IPC to the main process
3. Main process spawns (or reuses) the utility process worker
4. The worker runs `runAllAnalyzers()` — git stats, line counts, complexity,
   duplicates, imports, dead code, dependency audit, coverage parsing, all in one pass
5. Results come back as one `AnalysisResult` object, cached on disk keyed by repo path
   * current commit hash + a hash of the project config
6. The renderer stores it in a Zustand store; every tab derives what it needs from
   that one object via a single hook (`useAnalysis()`) — no tab fetches its own data
   independently

This matters for anyone adding a new piece of data: it should flow through
`AnalysisResult`, get added to the relevant analyzer in `src/main/analyzers/`, and get
exposed through `useAnalysis()` — not fetched ad hoc from a new IPC call inside a
specific tab, unless it's something that genuinely needs to be computed on demand
(see the next section).

## When something *isn't* part of the cached analysis

Not everything goes through the cached `AnalysisResult` pipeline. Some signals are
computed live, on demand, via a dedicated IPC call instead — typically because they
involve comparing two arbitrary git states (e.g. a staged diff vs. `HEAD`, or two
arbitrary commits) rather than "the current state of the repo," which is what the
cached pipeline is built around. The diff health score and post-commit complexity
toast in the Review tab are both built this way: a small main-process handler runs
`git show`/`git diff` against the specific refs needed, instead of waiting for or
triggering a full re-analysis.

## IPC conventions

Every IPC handler:

* Is wired in three places: `main/index.ts` (the handler itself), `preload/index.ts`
  (the exposed `window.analyzer.*` method), and `types/ipc.ts` (the `AnalyzerAPI`
  interface). Missing any one makes the method unreachable from the renderer with no
  compile error to catch it.
* Never throws. Returns `{ __error: string }` on failure; the preload layer checks for
  that shape and re-throws as a real `Error` on the renderer side, so renderer code can
  use normal `try`/`catch`.

## The renderer's data flow

`useAnalysis()` is the single hook every tab uses to read analysis data — no tab reads
the Zustand store's raw `data` field directly, and no tab fetches its own copy of
anything already in `AnalysisResult`. This keeps every tab's view of the data
consistent and means a schema change to `AnalysisResult` only needs updating in one
place to propagate everywhere it's consumed.

## MCP server

A separate, smaller process (`src/main/mcp-server.ts`), started with a `--mcp-server`
flag, speaking the [Model Context Protocol](https://modelcontextprotocol.io) over
stdio. It reads a lightweight `mcp-context.json` snapshot written after each analysis
— not a live connection into the running desktop app — so an AI agent's queries never
block or interfere with whatever you're doing in the UI. See the
[MCP tool reference](../reference/mcp-tools) for what it exposes.

## CLI

`packages/cli` is a separate npm package (`rei`) that imports the same analyzer code
directly from `src/main/analyzers/` — there's exactly one implementation of every
analyzer, used by both the desktop app and the CLI. See the
[CLI reference](../reference/cli).

## Where to look next

* [Design system](./design-system) for UI conventions if you're touching the
  renderer
* [Lessons learned](./lessons-learned) for non-obvious gotchas worth knowing before
  you hit them yourself
* `CONTRIBUTING.md` at the repo root for dev setup and the PR process
