Code Style Philosophy
Autonome enforces strict code style to maintain consistency and quality across the codebase:
- Zero tech debt: Do things right from the start, no workarounds
- Adaptive refactoring: Clean up sloppy code whenever you encounter it
- No bandaid fixes: Fix issues at their source, not with patches
- Complete implementations: Build for scale (>1000 users), not quick hacks
From AGENTS.md: “Early development, no users. No backwards compatibility concerns. Do things RIGHT: clean, organized, zero tech debt. Never create compatibility shims.”
Biome Configuration
Autonome uses Biome for linting and formatting instead of ESLint + Prettier:
- Faster than ESLint (10-100x speedup)
- Single tool for both linting and formatting
- Zero config required (sensible defaults)
- Native to the JavaScript ecosystem
Configuration File
Biome is configured in biome.json:
{
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}
Running Biome
# Check for issues (lint + format)
bun run check
# Format code
bun run format
# Lint code
bun run lint
# Auto-fix issues
bun run check --write
Run bun run check before committing to catch style violations early.
Core Style Rules
1. Indentation: Tabs
Always use tabs for indentation, never spaces:
// Good ✅
function calculatePnl(position: Position) {
const { side, quantity, averagePrice, currentPrice } = position;
if (side === "BUY") {
return (currentPrice - averagePrice) * quantity;
}
return (averagePrice - currentPrice) * quantity;
}
// Bad ❌ (spaces)
function calculatePnl(position: Position) {
const { side, quantity, averagePrice, currentPrice } = position;
if (side === "BUY") {
return (currentPrice - averagePrice) * quantity;
}
return (averagePrice - currentPrice) * quantity;
}
Why tabs?
- Accessibility: Users can configure tab width to their preference
- Smaller file sizes
- Easier to navigate with keyboard
2. Quotes: Double
Always use double quotes for strings:
// Good ✅
const message = "Hello, world!";
const symbol = "BTC-USD";
import { Button } from "@/components/ui/button";
// Bad ❌
const message = 'Hello, world!';
const symbol = 'BTC-USD';
import { Button } from '@/components/ui/button';
Exception: Use backticks for template literals:
const greeting = `Hello, ${name}!`;
const query = `
SELECT * FROM "Orders"
WHERE "status" = 'OPEN'
`;
3. Import Organization
Biome automatically organizes imports:
// Correct order (automatic):
// 1. External packages
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import * as Sentry from "@sentry/react";
// 2. Internal absolute imports
import { orpc } from "@/server/orpc/client";
import { Button } from "@/components/ui/button";
import { calculatePnl } from "@/core/shared/trading/calculations";
// 3. Relative imports
import { PositionCard } from "./PositionCard";
import { usePositions } from "../hooks/usePositions";
// 4. Type imports (grouped separately)
import type { Position } from "@/db/schema";
import type { ReactNode } from "react";
4. Semicolons
Biome enforces semicolons at the end of statements:
// Good ✅
const x = 5;
return calculatePnl(position);
// Bad ❌
const x = 5
return calculatePnl(position)
TypeScript Conventions
Type Annotations
Use explicit types for function parameters and return values:
// Good ✅
function calculatePnl(position: Position): number {
const pnl = (position.currentPrice - position.averagePrice) * position.quantity;
return pnl;
}
// Acceptable (return type inferred)
function getSymbol(position: Position) {
return position.symbol; // string inferred
}
// Bad ❌ (no parameter types)
function calculatePnl(position) {
return position.currentPrice - position.averagePrice;
}
Type vs Interface
Prefer type over interface for most cases:
// Good ✅
type Position = {
id: string;
side: "BUY" | "SELL";
quantity: string;
averagePrice: string;
};
// Use interface for extending
interface ExtendedPosition extends Position {
exitPlan?: ExitPlan;
}
Avoid any
Never use any - use unknown or proper types:
// Good ✅
function parseJson(data: string): unknown {
return JSON.parse(data);
}
const result = parseJson(jsonString);
if (typeof result === "object" && result !== null) {
// Safe to use
}
// Bad ❌
function parseJson(data: string): any {
return JSON.parse(data);
}
React Component Patterns
Component Structure
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
import { cva, type VariantProps } from "class-variance-authority";
// 1. Variants definition (if applicable)
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md font-medium",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground",
destructive: "bg-destructive text-destructive-foreground",
outline: "border border-input",
},
size: {
default: "h-10 px-4",
sm: "h-9 px-3",
lg: "h-11 px-8",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
// 2. Props interface
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
children: ReactNode;
}
// 3. Component definition
export function Button({ className, variant, size, ...props }: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
Hooks Conventions
Custom hooks must start with use:
// Good ✅
export function usePositions(modelId: string) {
return useQuery(
orpc.trading.getPositions.queryOptions({
input: { modelId },
}),
);
}
// Bad ❌
export function getPositions(modelId: string) {
// Not a hook name
}
Event Handlers
Prefix event handlers with handle:
function PositionCard({ position }: Props) {
const handleClose = () => {
// Close position logic
};
const handleEdit = () => {
// Edit logic
};
return (
<div>
<Button onClick={handleClose}>Close</Button>
<Button onClick={handleEdit}>Edit</Button>
</div>
);
}
Naming Conventions
Variables & Functions
camelCase for variables and functions:
const portfolioValue = 10000;
const currentPrice = 50.25;
function calculateUnrealizedPnl() {}
function getOpenPositions() {}
Constants
UPPER_SNAKE_CASE for constants:
const INITIAL_CAPITAL = 10000;
const MAX_POSITION_SIZE = 1000;
const API_BASE_URL = "https://api.example.com";
// Exception: Configuration objects use camelCase
const VARIANT_CONFIG = {
Apex: { color: "#FF6B6B", label: "Apex" },
Trendsurfer: { color: "#4ECDC4", label: "Trendsurfer" },
};
Types & Interfaces
PascalCase for types, interfaces, and enums:
type Position = {
id: string;
side: OrderSide;
};
interface PositionCardProps {
position: Position;
onClose: () => void;
}
enum OrderStatus {
OPEN = "OPEN",
CLOSED = "CLOSED",
CANCELED = "CANCELED",
}
Files & Directories
kebab-case for file names (except components):
src/
├── core/
│ └── shared/
│ └── trading/
│ ├── calculations.ts # kebab-case
│ └── fill-tracker.ts # kebab-case
├── components/
│ └── ui/
│ ├── Button.tsx # PascalCase for components
│ └── PositionCard.tsx # PascalCase for components
└── server/
└── features/
└── trading/
└── trading-repository.ts # kebab-case
Database Conventions
Quoted Identifiers
Always quote capitalized table and column names:
// Good ✅
const positions = await db
.select()
.from(schema.Orders)
.where(eq(schema.Orders.status, "OPEN"));
// SQL: SELECT * FROM "Orders" WHERE "status" = 'OPEN'
// Bad ❌
const positions = await db
.select()
.from(Orders) // Unquoted - will fail in PostgreSQL
.where(eq(Orders.status, "OPEN"));
Monetary Values
Store as TEXT, cast to NUMERIC for calculations:
// Schema definition
export const Orders = pgTable("Orders", {
id: text("id").primaryKey(),
averagePrice: text("averagePrice").notNull(),
realizedPnl: text("realizedPnl"),
});
// SQL queries
const totalPnl = await db.execute(sql`
SELECT SUM(CAST("realizedPnl" AS NUMERIC)) as total
FROM "Orders"
WHERE "status" = 'CLOSED'
`);
oRPC Patterns
Procedure Structure
import "@/polyfill"; // Always import at top
import { os } from "@orpc/server";
import { z } from "zod";
import * as Sentry from "@sentry/react";
export const getPositions = os
.input(
z.object({
modelId: z.string().optional(),
variant: z.enum(["Apex", "Trendsurfer", "Contrarian", "Sovereign"]).optional(),
}),
)
.output(
z.array(
z.object({
id: z.string(),
side: z.enum(["BUY", "SELL"]),
quantity: z.string(),
averagePrice: z.string(),
}),
),
)
.handler(async ({ input }) =>
Sentry.startSpan({ name: "getPositions" }, async () => {
const { modelId, variant } = input;
// Implementation
return positions;
}),
);
Client Usage
import { useQuery } from "@tanstack/react-query";
import { orpc } from "@/server/orpc/client";
function Positions() {
const { data: positions } = useQuery(
orpc.trading.getPositions.queryOptions({
input: { modelId: "model-123" },
}),
);
return <div>{/* Render positions */}</div>;
}
Critical Rules from AGENTS.md
1. Package Manager: Bun Only
# Good ✅
bun install
bun add package-name
bun run dev
# Bad ❌
npm install
pnpm add package-name
yarn dev
2. Data Fetching: oRPC Only
// Good ✅
const { data } = useQuery(
orpc.trading.getPositions.queryOptions({ input: {} }),
);
// Bad ❌
const response = await fetch("/api/positions");
const data = await response.json();
3. Environment Variables: T3Env
// Good ✅
import { env } from "@/env";
const apiKey = env.LIGHTER_API_KEY;
const dbUrl = env.DATABASE_URL;
// Bad ❌
const apiKey = process.env.LIGHTER_API_KEY;
const dbUrl = import.meta.env.DATABASE_URL;
4. No Workarounds
// Bad ❌ - Bandaid fix
function calculatePnl(position: any) {
// @ts-ignore
return position.currentPrice - position.entryPrice;
}
// Good ✅ - Proper fix
function calculatePnl(position: Position): number {
if (!position.currentPrice || !position.averagePrice) {
throw new Error("Missing price data");
}
return position.currentPrice - position.averagePrice;
}
5. Adaptive Refactoring
Before:
// Encountered sloppy code
function calc(p) {
return p.cp - p.ep;
}
After:
// Refactored while working nearby
function calculatePnl(position: Position): number {
const { currentPrice, averagePrice } = position;
return currentPrice - averagePrice;
}
6. Edge Case Exhaustion
// Good ✅ - All edge cases handled
function calculateWinRate(trades: Trade[]): number {
// Edge case: Empty array
if (trades.length === 0) {
return 0;
}
// Edge case: All trades are break-even
const winningTrades = trades.filter((t) => t.pnl > 0);
if (winningTrades.length === 0) {
return 0;
}
// Normal case
return winningTrades.length / trades.length;
}
Component Styling (Tailwind + CVA)
Using CVA for Variants
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-md px-2 py-1 text-xs font-semibold",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground",
success: "bg-green-500 text-white",
error: "bg-red-500 text-white",
},
},
defaultVariants: {
variant: "default",
},
},
);
interface BadgeProps extends VariantProps<typeof badgeVariants> {
children: React.ReactNode;
className?: string;
}
export function Badge({ variant, className, children }: BadgeProps) {
return (
<span className={cn(badgeVariants({ variant }), className)}>
{children}
</span>
);
}
Using cn() for Class Merging
import { cn } from "@/lib/utils";
// Merge classes with proper precedence
<div
className={cn(
"base-class p-4 bg-white",
isActive && "bg-blue-500", // Conditional
className, // User override
)}
/>
VS Code Integration
Add to .vscode/settings.json:
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
Pre-Commit Checklist
Before committing code:
Common Mistakes
Mistake 1: Using spaces instead of tabs
// Wrong
function foo() {
return "bar"; // 2 spaces
}
// Correct
function foo() {
return "bar"; // 1 tab
}
Fix: Run bun run format --write
Mistake 2: Single quotes
// Wrong
const message = 'Hello';
// Correct
const message = "Hello";
Fix: Run bun run format --write
Mistake 3: Unquoted database identifiers
// Wrong
await db.select().from(Orders);
// Correct
await db.select().from(schema.Orders); // Uses quoted "Orders"
Mistake 4: Direct process.env access
// Wrong
const apiKey = process.env.LIGHTER_API_KEY;
// Correct
import { env } from "@/env";
const apiKey = env.LIGHTER_API_KEY;
Mistake 5: Not organizing imports
// Wrong
import { Button } from "./Button";
import { useState } from "react";
import { env } from "@/env";
// Correct (Biome auto-organizes)
import { useState } from "react";
import { env } from "@/env";
import { Button } from "./Button";
Fix: Run bun run check --write
Next Steps
- Set up your editor: Configure VS Code with Biome extension
- Practice: Follow these conventions in your daily work
- Review: Read Testing for QA workflow
- Contribute: See Contributing Guidelines for the full development process
Consistency is more important than personal preference. Follow the style guide even if you disagree with specific choices.