# CircleHealth v2

Healthcare (PHI-handling) production monorepo. We rebuilt from scratch ("v2"); the old app lives on as `packages/legacy`. The bar for every change: a senior engineer can merge your diff after a skim, because correctness is checkable by the type system and the test suite — not by close reading.

## Stack

Turborepo · TypeScript (strict, `noUncheckedIndexedAccess`) · Next.js App Router · ChakraUI v3 · Supabase (Postgres) · Drizzle (schema) · Kysely (queries) · tRPC v11 · Zod · Biome · Vitest · Playwright · LangChain/LangSmith · LaunchDarkly · Vercel Queues.

## Non-negotiables
1. **Everything is typed.** No `any`, `as any`, `as unknown as`, `@ts-ignore`, `@ts-expect-error`. If types fight you, the design is wrong — fix the design. `noUncheckedIndexedAccess` is on: indexed access is `T | undefined`; handle it, don't assert it away.
2. **Never the `interface` keyword — use `type`.** Enforced by Biome (`style/useConsistentTypeDefinitions`), so it fails lint, not review. Behavioral contracts are abstract classes (`BaseService`, `Jobbable`, `EMRAdapter`, `BaseRunner`), not interfaces. **The one exception is declaration merging**, which `type` cannot do: `declare module "@repo/feature-flags" { interface FeatureFlagRegistry }`, `declare global { interface Window }`, and any base interface other packages augment. It is not a `.d.ts`-only exception — `apps/web/lib/appcues.tsx` and `packages/feature-flags/src/types.ts` both need it, and converting either breaks the build. The rule is off for `**/*.d.ts`; elsewhere use a one-line `biome-ignore` naming the merge. Classes, constructor DI, and inheritance are encouraged where a family of implementations shares one contract.
3. **Always throw `CircleError` subclasses (`@repo/errors`), never `Error`.** Static messages, context via `.with({ ... })`, never PHI/PII. Prefer `safe()` (`@repo/safe`) over try/catch; `unwrap()` and let errors bubble in tRPC.
4. **DRY — check `@repo/utils` and sibling code first.** Generic helpers go IN `@repo/utils`, not next to your feature. Known repeat offenses: local `DEFAULT_TIMEZONE` consts (use `DEFAULT_FACILITY_TIME_ZONE` from `@repo/utils/time`), new `createLogger` wrappers (use `@repo/logger`), copy-pasted filter helpers. DRY is not a license to fragment: a helper earns its place only when the code repeats more than 3 times, or runs over 10 lines and deserves its own tests. Below that bar keep it inline, since reading the code beats clicking into a helper; a fragment moved into a function or a `*.helper.ts` file to look clean is indirection. A service is not a helper and can be two lines when how it is called or run justifies it. Date logic is the exception and can go in `@repo/utils/time` freely.
5. **Names carry the meaning; comments are a last resort.** A name that needs a comment is the wrong name — rename it. Functions are verbs (`findTagsMostRowsShare`, not `sharedTags`). The same goes for values: a literal nobody can change safely without already knowing what it means — a grid template, a timeout, a z-index, a magic index — gets a name, and a string or array packing several of them gets a table that pairs each value with the name it answers to. Code must be self-documenting: a comment not vital to disambiguating it does not ship. Merged work here runs about 2 comment blocks per 100 lines; materially above that needs justifying, block by block. Four kinds survive — a non-obvious *why* the code cannot state, a measured constant with its measurement, the bug a guard prevents, a PHI or security warning. The rest go on sight: restating the line below it, narrating a design decision or a plan, defending code nothing uses yet, a paragraph over a short function, the same rationale pasted into sibling files. Multi-line prose over a schema table is this repo's worst offender; `// Helpers` dividers are house style, keep them. The ones that slip through are the well-written, plausible ones, so reviewers delete comments on sight and a diff that adds them costs a round trip.

6. **Hooks are for business logic.** Components render; colocated hooks in the feature's `lib/` hold logic, data wiring, and state.
7. **Test everything.** Every repo method, service, procedure, hook, and non-trivial component gets a colocated test that fails without your change.
8. **Apps compose `@repo/*` packages** — they don't accumulate logic. In `apps/web`, never import `@repo/db` root (lint-enforced); use `@repo/db/schema` / `@repo/db/db.types` (note: NOT `@repo/db/types` — the biome error message says that, but the subpath doesn't exist). No deep `@repo/x/src/...` imports — packages expose `exports` subpaths.
9. **Find prior art before writing.** The pattern you need exists — grep a sibling (another repo, router, jobbable) and match its shape exactly. Read the package README of anything you touch.
10. **Every `if`/`else`/loop body gets braces, and ternaries never nest.** No `if (x) return;` — the body goes on its own line, early returns included. Two branches may be one ternary; three or more become a small named helper with early returns (`getGreeting` in `apps/web/app/(app)/page.tsx`), not a chained ternary, `let` + reassignment, or an IIFE. Biome doesn't enforce either, so existing code still has both: don't copy it, and don't sweep it outside the lines you're changing.

## Where things live

`apps/web` (the product), `apps/desktop` (Electron shell + recording tray), `apps/storybook`. ~40 packages; the load-bearing ones: `db`, `services`, `trpc`, `ai-agents`, `ai`, `ui` / `ui-domain`, `vqs` + `jobs`, `auth` / `auth-next`, `errors` + `safe`, `utils`, `feature-flags`, `observability`. `packages/legacy` (81k LOC) holds all EHR integrations and the v1 chart-review stack — see danger zones.

Deep guidance is in skills (`.claude/skills/`) — db-layer, trpc-api, errors, web-ui, background-jobs, testing, ehr-integrations, ai-agents, services-layer, backward-compat, circle-code-review, sniffer (browser QA of a PR) — and in `docs/` (start at `docs/README.md`; `docs/15-agent-harness.md` explains the harness itself; `docs/08` errors, `docs/13` jobs, `docs/14` auth are authoritative). If an older doc contradicts a skill, the skill wins.

## Danger zones — stop and ask a human

- `packages/legacy/src/index.ts` (the client-safe barrel — never add exports)
- EHR integration clients (`packages/legacy/src/integrations/` — vendor rate limits, PHI)
- Migrations touching RLS; auth middleware and session/cookie logic
- LangSmith prompt snapshots; anything flag-split between two live implementations

## Definition of done

**Prove, don't claim.** While iterating, run the narrowest relevant slice (`pnpm --filter <pkg> test -- <pattern>`); confirm new tests fail without your change before trusting that they pass with it. Then the full trio: `pnpm check-types && pnpm lint && pnpm test` — paste evidence in the PR. PR title: `[DEV-XXXX]` / `[NA]` / `[HF]` + `type(scope): summary`. State which procedure tier / placement rule you applied. Keep PRs under 500 added lines (SQL and generated files exempt). Edited `CLAUDE.md` or `.claude/commands/`? Run `pnpm install-skills` and commit the regenerated `AGENTS.md` / `.cursor/skills/` in the same PR — nothing regenerates them for you.
