CLAUDE.md
Rama main · réplica de lectura del repositorio original.
# LangWatch
LLM Ops platform: evaluation, observability and optimisation of AI agents. One
pnpm workspace (TypeScript), plus Go and Python services.
<!-- Keep this file for what nearly every session needs; target under 10 KB.
Area guidance lives in .claude/rules/*.md (path-scoped), procedures in
.claude/skills/, reference in dev/docs/. Link, don't @-import. -->
## Where the truth lives
In this order. When they disagree, the higher one wins and the lower one is the
defect.
1. **The linters.** `pnpm lint` (oxlint + the `langwatch` plugin, every rule at
`error`) and `pnpm lint:architecture` (whole-tree policies). Every finding
carries its own fix: do what it says. Never game a rule by renaming.
2. **`dev/docs/ARCHITECTURE.md`**, the architecture record. Read the relevant
section before composing a process, writing a module or citing a shape.
§15 lists deleted spellings (writing one is a defect); §16 maps target
names that have not landed yet to today's names.
3. **`specs/` and `modules/<name>/specs/`**: feature files are the
requirements. If no scenario covers your task, write one first, error paths
included.
Operating discipline (what you may run and edit, git in a shared checkout,
secrets, cost) is canonical in `.claude/skills/core/repository-rules.md`; tests
in `.claude/skills/core/testing-rules.md`. Read them before running checks or
touching git.
## Layout
```
apps/ ui (Vite-built SPA the api serves; Vite dev server only under --hmr)
· api (tRPC+REST+SSE :6560) · worker (queues, projections, subscribers)
· tasks (migrations/backfills, run before serve) · server (npx CLI)
· scenario-child. An app is main.ts + config.ts; no product code.
modules/<name>/ one feature: contract/ · process/ · browser/ · client/ (+ specs/, adrs/)
modules/catalogue.json the one map of subject -> owning module
enterprise/modules/ same shape, entitlement-gated at runtime
packages/ framework only; feature code here is a defect
services/ aigateway, nlpgo, langyagent (Go) · langevals (Python)
sdks/ python · typescript (the published `langwatch` SDK, not the product) · go
specs/ tools/ (thuishaven = haven) mcp/typescript/ docs/ (public docs site)
```
ui, api and worker always run together: a stack missing the worker serves pages
and silently processes no jobs.
Inside a module: `process/src/` holds `<name>.module.ts`, `app/`, `services/`,
`repositories/`, `channels/`, `eventing/`, `transport/`, `rules/`, `tasks/`,
`migrations/`; `browser/src/` holds `model/` → `behavior/` → `ui/elements|blocks|sections`.
The filename grammar is `packages/oxlint-rules/grammar/feature-layout-policy.mjs`.
Path-scoped rules in `.claude/rules/` load the details when you open those files.
## Rules that hold everywhere
1. **A module is an isolated microservice.** Other code sees only its contract
and calls its `*Api` token. Reaching into another module's services,
repositories, channels, tables or browser package is a violation even when
the import resolves.
2. **A repository belongs to one module**, takes only its own store, and is
called only by that module's services. Owning a table means owning every
query against it.
3. **Behaviour lives in `services/`**: repository = owned state, channel =
messages to anything the module doesn't own, service = behaviour over both.
Transports, repositories, channels and module classes stay thin.
4. **Comments: five lines maximum**, delimiters included (lint error at six,
unsuppressible). Longer reasoning goes in an ADR (`dev/docs/adr/`) or spec
with a one-line link. Delete comments that restate the code.
5. **Named parameters** (`fn({ a, b })`), top-level `import`/`import type` only
(no inline `import()`), Zod + `infer` instead of duplicated TS types.
6. **A route's authorization is declared, never hand-rolled.** Name the
permission with `.withPermission(...)`; the door asks it before the handler
runs. `.withAccess(anyAuthenticated(...))` followed by a permission check in
a middleware context, the handler or the `*Api` is a bypass. When the library
cannot express the check, extend `packages/api` and the door; don't route
around them. The application asks only what depends on the loaded row.
7. **Product analytics and lifecycle messaging belong to nurturing.** A module
records a fact event from its service (`subscription_started`,
`user_created`); nurturing subscribes and talks to PostHog and Customer.io.
No other module holds an analytics channel, client or key.
## Verify every change
After each change, scoped to the paths you touched:
```bash
pnpm exec oxfmt --write --disable-nested-config <paths>
pnpm exec oxlint --quiet --type-aware --config .oxlintrc.jsonc <paths>
pnpm lint:changed # or: your changes and dependents, cached
VITEST_MAX_WORKERS=2 pnpm --filter <package> test <paths> # never npx vitest; no `--`
tsc --noEmit --ignoreConfig <file> # one file while iterating
```
At the end of the task, once: `pnpm --filter <package> typecheck` for each
package touched, and `pnpm lint:architecture --policies <ids>` for whole-tree
policies you may have affected (`--list-policies` lists them). Spec binding:
`pnpm --filter @langwatch/architecture-enforcer check:feature-parity`; read the
verdict banner.
Whole-repo `pnpm typecheck`, `pnpm lint` and `pnpm format` hold several GiB and
every core. Run them only when the task is to finish and ship, never as a lane
working under the coordinator, and never `pnpm format` while others share the
checkout. Heavy commands queue through haven's machine-wide gate (a PreToolUse
hook `haven up` installs); a queued run says so on stderr. That is a wait, not a
hang. Don't bypass it with a raw `tsc -b`.
If the workspace is red from unrelated work, prove your slice and report the
remaining diagnostics exactly. Don't call a blocked check green.
## Commands
```bash
pnpm install # root only; one lockfile. Narrow: --filter "<pkg>..."
pnpm start:prepare:files # generated files (Prisma client, etc.); fixes "Cannot find module"
pnpm generate:modules # after editing modules/catalogue.json
pnpm sync:references # nx sync: project references, after a dependency change
pnpm test:affected # Nx: only packages this change reached, cached
pnpm test:changed [files...] # only the test files this change reaches by imports, any package
pnpm typecheck # one root tsc -b; incremental, rechecks only what changed
pnpm typecheck:src # same, without the test programs: the local default; CI runs the full one
pnpm start:prepare:db # upgrade + system-migrations pass; the api refuses until it ran
make go-lint-changed # Go: lints your uncommitted edits (never raw golangci-lint)
```
The root `test`, `typecheck` and `lint` scripts run through Nx (ADR-150): use
the `pnpm` scripts above rather than `nx` directly; the Nx plugin's skills cover `nx` itself.
## Dev stack
`make haven up` starts this worktree's stack at
`app.<slug>.langwatch.localhost` (UI; `/api` for the API). `haven status`,
`haven logs api|ui|go -f`; add `--agent` when driving haven as an agent. Plain
`pnpm dev` also works (ports derive from `PORT`, default 5560). Full reference,
including the no-container setup: `dev/docs/LOCAL_STACK.md`; when it won't come
up, the `haven` skill (`troubleshooting.md`).
`.env` lives at the workspace root. **Never read `.env` or print a secret**;
values resolve through `@langwatch/secrets` (ADR-132) and `haven env` masks
them. Module code never reads `process.env`.
## Git and GitHub
- Other changes in the checkout belong to someone else. Stage explicit paths,
never `git add -A`/`.`, never `git stash`; check `git diff --cached --stat`
before committing. Don't push or open a PR unless asked.
- British English in prose; no em dashes; no attribution lines or agent names in
commits, PRs or specs. Never name a customer: the repo is public.
- Review feedback is one round per batch, never one push per comment. Read every
open thread, fix the real ones, reply to the wrong ones (leave those threads
open: the review bot only accepts a reply on an open thread), push once, then
ask for one re-review with `@coderabbitai review`. A lint, format or PR-body finding is CI's job: run the tool,
never hand-edit for it. Fix the PR description just before merge; editing it
needs no new review.
- Commit messages are Conventional Commits (`feat(scope): …`, `fix: …`).
- Branches: `issue123/slug` for issues, `feat/slug` for features.
- Worktrees go in `.worktrees/<branch-with-slash-as-hyphen>` via
`make worktree <issue|name>`; never `.claude/worktrees/`, a sibling checkout
or `/tmp`. More in `dev/docs/best_practices/git.md`.
- `gh pr edit --body` → `gh api repos/OWNER/REPO/pulls/N -X PATCH -f body="…"`.
- `gh api graphql`: inline values in the query; `-f`/`-F` break on multiline.
- Don't trust `gh pr checks` alone (it dedups by name); use
`gh run list --branch <branch>`.
## Other traps
- **Subagents:** every spawn names model, effort and context size plus one clause
why; routing table in `.claude/coordinator/COORDINATOR.md` §3.
- **Dogfooding:** `langwatch login`/`instrument` rewrite machine-global config.
First export `LANGWATCH_CLI_CONFIG`, `CLAUDE_CONFIG_DIR` and `CODEX_HOME`
under `<worktree>/.claude/tmp/dogfood/` (`dev/docs/best_practices/dogfooding-isolation.md`).
Test agent-usage features in a real interactive session in a sub-tmux, not
`claude -p`, and check the data landed in the product.
- **Local binaries** go in `.bin/<name>/<name>` (ignored).
- **rtk** is optional here. Check `command -v rtk` once; if present, prefix each
command in a chain (`rtk git status`, `rtk pnpm …`); if absent, run unprefixed.
`rtk --help` lists its filters.
## References
`dev/docs/ARCHITECTURE.md` · `dev/docs/CODING_STANDARDS.md` ·
`dev/docs/TESTING_PHILOSOPHY.md` · `dev/docs/lint-rules.md` (generated) ·
`dev/docs/best_practices/` · `dev/docs/adr/` · `dev/docs/LOCAL_STACK.md`