Skip to content

AI Agent Guide

This guide is for AI coding agents working in the Cosella codebase. It defines the rules, patterns, and constraints that every agent must follow to produce correct, reviewable contributions.

Before writing any code, every agent MUST:

  1. Run pnpm typecheck to confirm the baseline is clean
  2. Run pnpm test to confirm tests pass
  3. Identify which package(s) are affected
  4. Never modify more packages than necessary
cosella/
├── apps/ # Runnable applications (6)
│ ├── dashboard/ # Rep-facing sales dashboard (React + Vite + TanStack Router)
│ ├── admin/ # Internal admin panel (React + Vite + TanStack Router)
│ ├── desktop/ # Electron desktop copilot (Electron + React + Vite)
│ ├── docs/ # Documentation site (Astro + Starlight)
│ ├── marketing/ # Public landing page (Astro + React)
│ └── storybook/ # Component stories (Storybook 9 + Vite)
├── packages/ # Shared libraries (13)
│ ├── ui/ # React component library (25+ components)
│ ├── tokens/ # Design tokens (CSS + JS)
│ ├── domain/ # Shared domain types with Zod schemas
│ ├── api-client/ # Generated OpenAPI types + TanStack Query hooks
│ ├── realtime/ # Typed WebSocket client
│ ├── auth/ # Auth provider, session store
│ ├── permissions/ # Capability-based permission helpers
│ ├── testing/ # MSW server, handlers, fixtures
│ ├── observability/ # Error boundaries, Sentry integration
│ ├── app-shell/ # AppShell layout, Sidebar, TopBar
│ ├── forms/ # React Hook Form + Zod integration
│ ├── audio-capture/ # Audio capture (Web + Electron)
│ └── audio-engine/ # Audio pipeline, VAD, Opus encoding
└── tooling/ # Build config packages (3)
├── eslint-config/ # Shared ESLint configs
├── ts-config/ # Shared TypeScript configs
└── plop-templates/ # Handlebars templates for generators

These are enforced by eslint-plugin-boundaries at lint time. Violations will fail CI.

apps ──────> packages (one direction only, never reverse)
tokens ─────> (nothing internal)
domain ─────> (nothing internal)
ui ─────────> tokens ONLY
api-client ─> domain
realtime ───> domain
auth ───────> domain
permissions > domain + auth (declared exception)
testing ───> domain
observability > ui + domain (NEVER auth)
app-shell ──> auth + tokens + ui
forms ──────> ui
audio-capture > domain
audio-engine > domain

Rule: Apps may depend on packages — never the reverse. Packages may not depend on sibling apps.

The tsconfig uses strict mode with these additional strictness flags:

  • exactOptionalPropertyTypes: trueundefined must be explicit in union types
  • noUncheckedIndexedAccess: true — index access returns T | undefined
  • noUnusedLocals: true — no unused imports or variables
  • noUnusedParameters: true — no unused function parameters
// ❌ DO NOT use `as` casts
const x = data as SomeType;
// ❌ DO NOT use `any`
const x: any = data;
// ❌ DO NOT use `!` non-null assertions
const el = document.getElementById('x')!;
// ❌ DO NOT use forwardRef (React 19)
const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => ...);
// ✅ Use type narrowing
function isString(value: unknown): value is string {
return typeof value === 'string';
}
// ✅ Use ref as prop (React 19)
function Button({ ref, ...props }: ButtonProps & { ref?: React.Ref<HTMLButtonElement> }) {
return <button ref={ref} {...props} />;
}
// ✅ Use cn() for className merging
import { cn } from '../lib/utils.js';
<div className={cn('base-class', conditional && 'active', className)} />
// ✅ Use consistent-type imports
import type { ButtonProps } from './Button.js';
packages/ui/src/
├── button/
│ ├── Button.tsx # PascalCase component file
│ ├── Button.stories.tsx # Storybook stories
│ └── Button.test.tsx # Vitest tests
// 1. React / external libraries
import { useState, useEffect } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
// 2. Internal packages (workspace references)
import { cn } from '../lib/utils.js';
import type { ButtonProps } from './Button.js';
// 3. Local imports
import { someLocalUtil } from './utils.js';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '../lib/utils.js';
const componentVariants = cva('base-classes', {
variants: {
variant: {
primary: 'primary-classes',
secondary: 'secondary-classes',
},
},
defaultVariants: {
variant: 'primary',
},
});
export interface ComponentProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof componentVariants> {
customProp?: boolean;
}
export function Component({
className,
variant,
customProp,
children,
ref,
...props
}: ComponentProps & { ref?: React.Ref<HTMLDivElement> }) {
return (
<div
ref={ref}
className={cn(componentVariants({ variant }), className)}
{...props}
>
{children}
</div>
);
}

Use the Plop generator — never create a package by hand:

Terminal window
pnpm gen package

This creates:

  • packages/<name>/package.json
  • packages/<name>/tsconfig.json
  • packages/<name>/src/index.ts

Then add the new package as a workspace:* dependency where needed and re-run pnpm install.

Terminal window
pnpm gen component

This creates:

  • packages/ui/src/<kebab-name>/<Name>.tsx
  • packages/ui/src/<kebab-name>/<Name>.test.tsx
  • Export appended to packages/ui/src/index.ts
  1. Always use cn() for className merging (never string concatenation)
  2. Always use cva() for variant-based styling
  3. Always accept className prop for consumer overrides
  4. Always export the props interface alongside the component
  5. Always use ref as prop (React 19 — no forwardRef)
  6. Always use WAI-ARIA patterns for accessibility
  7. Always use Lucide React for icons

Ship with a Storybook story. Add play interactions when there is behaviour worth asserting. Vitest unit tests are optional and usually redundant with play.

Every new exported function needs a Vitest test. Includes:

  • permissions
  • observability
  • realtime
  • api-client
  • auth
  • Any future package whose value is behaviour rather than markup

Feature slices with meaningful logic get tests; pure-render route components generally do not.

Must use the MSW handlers from @cosella/testing:

import { http, HttpResponse } from 'msw';
import { server } from '@cosella/testing';
// In your test
server.use(
http.get('/api/calls', () => {
return HttpResponse.json({ calls: [] });
}),
);

Use queries by role, text, label — not test IDs:

// ✅ Good
screen.getByRole('button', { name: /submit/i });
screen.getByText('Welcome back');
screen.getByLabelText('Email');
// ❌ Bad
screen.getByTestId('submit-button');

Follow Conventional Commits. The commit message must pass commitlint. Format:

feat(package-name): short description under 72 chars

Scope = the package or app name without the @cosella/ prefix.

Terminal window
feat(ui): add ProgressBar component
fix(api-client): handle network timeout errors
docs(readme): update installation guide
refactor(permissions): simplify capability checks
test(realtime): add WebSocket reconnection tests
style(ui): adjust Button hover transition

ADRs live at apps/docs/src/content/docs/engineering/adr-NNNN.mdx. When making a load-bearing architectural decision, write (or update) an ADR before the implementation PR.

---
title: ADR-NNNN: Decision Title
description: Brief summary of the decision.
---
# ADR-NNNN: Decision Title
## Status
Proposed | Accepted | Deprecated | Superseded by [ADR-NNNN](/engineering/adr-NNNN)
## Context
What is the issue that we're seeing that motivates this decision?
## Decision
What is the change that we're proposing and/or doing?
## Consequences
What becomes easier or more difficult to do because of this change?
### Positive
- ...
### Negative
- ...
  1. Read CLAUDE.md and AGENTS.md before making any changes
  2. Run pnpm typecheck after every change
  3. Never modify routeTree.gen.ts manually
  4. Never modify packages/api-client/src/api.gen.ts manually
  5. Never connect packages/auth to a real backend without explicit instruction
  1. Respect the file boundaries — don’t suggest changes outside the current package
  2. Use the existing patterns — look at neighboring files before writing new code
  3. Import types with import type when the import is type-only
  4. Don’t suggest as casts — suggest type narrowing instead
  1. Never commit secrets or keys to the repository
  2. Never disable ESLint rules — fix the underlying issue instead
  3. Never skip tests by inferring a “convention” from missing files
  4. Always verify with pnpm typecheck and pnpm lint before suggesting completion
  5. Always follow the existing code patterns in the surrounding context
  • Do not run pnpm add with arbitrary package versions — use catalog: entries from pnpm-workspace.yaml
  • Do not edit routeTree.gen.ts manually — it is regenerated on pnpm dev/pnpm build
  • Do not edit packages/api-client/src/api.gen.ts manually — regenerate with pnpm gen:api
  • Do not connect packages/auth to a real backend without explicit instruction
  • Do not create docs/engineering/adr/ at the repo root — ADRs go in the Starlight docs source
  • Do not skip tests by inferring a “convention” from missing files
  • Do not use as casts unless absolutely necessary and always add a comment explaining why
  • Do not use any except in generated files (routeTree.gen.ts, api.gen.ts)
  • Do not use ! non-null assertions — narrow the type properly
Terminal window
# Development
pnpm dev # Start all apps
pnpm --filter @cosella/dashboard dev # Start dashboard only
pnpm --filter @cosella/storybook dev # Start Storybook
# Code Quality
pnpm typecheck # Type check all packages
pnpm lint # Lint all packages
pnpm test # Run all tests
# Code Generation
pnpm gen component # Generate new UI component
pnpm gen package # Generate new package
pnpm gen:api # Regenerate API client from OpenAPI spec
# Build
pnpm build # Build all packages and apps
FilePurpose
CLAUDE.mdClaude Code specific instructions
AGENTS.mdAI agent general instructions
docs/REVIEW.mdCode review standard (354 lines)
packages/ui/src/index.tsAll UI component exports
packages/tokens/src/theme.cssAll design tokens
packages/tokens/src/components.cssShared CSS component classes
pnpm-workspace.yamlWorkspace definition + catalog versions
turbo.jsonTurborepo pipeline config
plopfile.mjsCode generator definitions