Skip to content

Instantly share code, notes, and snippets.

@josemvcerqueira
Last active January 20, 2026 00:44
Show Gist options
  • Select an option

  • Save josemvcerqueira/2298414ed26869285324f716ea8e31f6 to your computer and use it in GitHub Desktop.

Select an option

Save josemvcerqueira/2298414ed26869285324f716ea8e31f6 to your computer and use it in GitHub Desktop.
Jose's CLAUDE.md

CLAUDE.md

Instructions for AI assistants working on this codebase.


How to Use This File

  1. Replace [PROJECT_NAME] and [DESCRIPTION] with your project details
  2. Remove language sections you don't use
  3. Fill in the Project Configuration section at the end
  4. Delete this "How to Use" section after customizing

Project Overview

[PROJECT_NAME]: [DESCRIPTION]

Directory Structure:

project/
├── contracts/     # Smart contracts
├── sdk/           # TypeScript SDK
├── server/        # Backend server
├── scripts/       # Tooling and deployment
└── docs/          # Documentation

Agent Behavior

How to approach tasks in this codebase.

Task Execution

  1. Understand first - Read relevant files before making changes
  2. Verify assumptions - Check existing patterns in the codebase
  3. Incremental changes - Small, testable changes over large refactors
  4. Validate after changes - Run build and tests before completing

Decision Framework

Ask for clarification when:

  • Requirements are ambiguous
  • Multiple valid approaches exist with significant tradeoffs
  • Changes affect public APIs or interfaces
  • Unsure about business logic intent

Proceed autonomously when:

  • Task is well-defined
  • Following established codebase patterns
  • Changes are easily reversible
  • Standard refactoring or bug fixes

Change Principles

  1. Minimize blast radius - Touch only what's necessary
  2. Preserve patterns - Match existing code style and architecture
  3. No surprise dependencies - Discuss before adding new packages
  4. Single responsibility - One logical change per commit

Universal Rules

These rules apply to ALL code in this project.

Code Quality

  1. No comments - Code must be self-documenting through clear naming
  2. No TODOs/FIXMEs - Complete implementations before committing
  3. No dead code - Delete unused code; git has history
  4. No magic values - Use named constants
  5. No ignored errors - Handle or propagate every error
  6. No re-exports - Use export directly on declarations

Implementation Standards

Write optimal code on the first attempt. These patterns are non-negotiable.

  1. Bitwise for byte conversion - No string intermediates

    // ✓ Direct bitwise (little-endian bytes to bigint)
    let result = 0n;
    for (let i = bytes.length - 1; i >= 0; i--) {
      result = (result << 8n) | BigInt(bytes[i]);
    }
    
    // ✗ String intermediates
    const hex = bytes.map(b => b.toString(16).padStart(2, '0')).join('');
    return BigInt('0x' + hex);
  2. Single-pass algorithms - One iteration, not chained methods

    // ✓ Single pass
    let sum = 0;
    for (const x of arr) sum += x * 2;
    
    // ✗ Multiple passes
    arr.map(x => x * 2).reduce((a, b) => a + b, 0);
  3. Parallel when independent - Don't await sequentially

    // ✓ Parallel
    const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);
    
    // ✗ Sequential when independent
    const a = await fetchA();
    const b = await fetchB();
    const c = await fetchC();
  4. Pre-compute constants - Move computation out of hot paths

    // ✓ Compute once
    const VALIDATOR = buildValidator();
    const validate = (data: Data) => VALIDATOR.check(data);
    
    // ✗ Recompute every call
    const validate = (data: Data) => buildValidator().check(data);
  5. Uint8Array for binary - number[] only at serialization boundaries

    // ✓ Internal binary storage
    readonly #bytes: Uint8Array;
    
    // ✗ Array for internal binary
    readonly #bytes: number[];
  6. Actionable errors - Include what failed AND what's expected

    // ✓ Actionable
    invariant(bytes.length === 32, `Expected 32 bytes, got ${bytes.length}`);
    
    // ✗ Vague
    invariant(bytes.length === 32, 'Invalid length');
  7. Fail fast - Validate inputs at function entry

    // ✓ Validate upfront
    const process = (order: Order) => {
      invariant(order.items.length > 0, 'Order must have items');
      invariant(order.total > 0, 'Total must be positive');
      // ... logic
    };
  8. Typed errors - Structured errors, not ad-hoc strings

    // ✓ Typed
    class ValidationError extends Error {
      constructor(public field: string, public reason: string) {
        super(`${field}: ${reason}`);
      }
    }
    
    // ✗ String errors
    throw new Error('validation failed');

Naming Conventions

Type Convention Example
Constants UPPER_SNAKE MAX_RETRIES, API_TIMEOUT
Functions camelCase fetchUser, parseResponse
Types PascalCase UserProfile, ApiResponse
Variables camelCase userName, isValid
Files (TS) kebab-case user-profile.ts
Files (Rust) snake_case user_profile.rs
Test files source + .test user-profile.test.ts

Security

  1. Validate inputs - At system boundaries
  2. No hardcoded secrets - Use environment variables
  3. Defensive copies - Return new Uint8Array(this.#bytes), not this.#bytes
  4. Bounds checking - Use bigint for large numbers, validate ranges
  5. Parameterized queries - Never string concatenation for SQL
  6. Sanitize output - Escape user input before rendering

Anti-Patterns

Patterns to actively avoid.

Universal

  • ❌ Catching errors without handling or re-throwing
  • ❌ Mutable state in module scope
  • console.log in production (use structured logging)
  • ❌ Mixing concerns in single functions

TypeScript

  • as any or @ts-ignore to bypass type errors
  • export { x } at end of file (export inline instead)
  • ❌ Optional chaining without handling undefined case
  • ❌ Non-null assertion ! without invariant

Rust

  • .unwrap() outside tests
  • .clone() to satisfy borrow checker without understanding
  • async on functions that don't await
  • impl Trait return when concrete type is clearer

Move

  • transfer::public_transfer for objects that should be shared
  • ❌ Large vectors in objects (use dynamic fields)
  • ❌ Missing ctx: &mut TxContext (breaks upgrades)
  • object::delete on objects with dynamic fields (leaks storage)

Testing Philosophy

Principles

  1. One assertion per test - Multiple focused tests over one large test
  2. Descriptive names - test_withdraw_fails_when_balance_insufficient
  3. AAA pattern - Arrange, Act, Assert
  4. Test behavior, not implementation - Focus on inputs and outputs

What to Test

Priority What
Always Public APIs, edge cases, error conditions
Usually Complex private helpers, state transitions
Never Trivial getters, framework internals

Test Organization

__tests__/
├── unit/           # Isolated function tests
├── integration/    # Cross-module tests
└── e2e/            # Full system tests

Dependency Management

Adding Dependencies

  1. Check if functionality exists in current deps
  2. Prefer well-maintained, widely-used packages
  3. Pin exact versions in production
  4. Document why each dependency was added

Version Pinning

// package.json - exact versions
"dependencies": {
  "axios": "1.6.0"
}
# Cargo.toml - exact versions
[dependencies]
tokio = "=1.35.0"

TypeScript

Commands

pnpm install      # Install dependencies
pnpm build        # Build project
pnpm test         # Run tests
pnpm lint         # Run linter
pnpm lint:fix     # Auto-fix lint issues
pnpm typecheck    # Type checking only

File Structure

src/
├── index.ts           # Public exports
├── client.ts          # Main implementation
├── client.types.ts    # Types and interfaces
├── constants.ts       # Constants
├── utils/             # Utilities
└── __tests__/         # Tests

Type Naming

Pattern Suffix Example
Constructor args ConstructorArgs ClientConstructorArgs
Method args Args FetchUserArgs
External API shapes Raw UserRaw, OrderResponseRaw
Internal types (none) User, Order
Events (past tense) UserCreated, OrderPlaced
Configuration Config ApiConfig
Options Options FetchOptions

Class Pattern

import invariant from 'tiny-invariant';
import { API_URL, TIMEOUT_MS } from './constants';
import type { ConstructorArgs, FetchUserArgs, User, UserRaw } from './client.types';

export class ApiClient {
  static parseUser(raw: UserRaw): User {
    return {
      id: raw.id,
      name: raw.user_name,
      createdAt: new Date(raw.created_at),
    };
  }

  readonly #baseUrl: string;
  readonly #timeout: number;

  constructor({ baseUrl = API_URL, timeout = TIMEOUT_MS }: ConstructorArgs = {}) {
    this.#baseUrl = baseUrl;
    this.#timeout = timeout;
  }

  async fetchUser({ userId }: FetchUserArgs): Promise<User> {
    const response = await fetch(`${this.#baseUrl}/users/${userId}`, {
      signal: AbortSignal.timeout(this.#timeout),
    });
    invariant(response.ok, `Fetch failed: ${response.status}`);
    return ApiClient.parseUser(await response.json());
  }
}

Value Object Pattern

export class Address {
  static readonly LENGTH = 32;
  static readonly ZERO = new Address(new Uint8Array(Address.LENGTH));

  static fromHex(hex: string): Address {
    const normalized = hex.startsWith('0x') ? hex.slice(2) : hex;
    invariant(normalized.length === Address.LENGTH * 2, `Invalid hex length: ${normalized.length}`);
    const bytes = new Uint8Array(Address.LENGTH);
    for (let i = 0; i < Address.LENGTH; i++) {
      bytes[i] = parseInt(normalized.slice(i * 2, i * 2 + 2), 16);
    }
    return new Address(bytes);
  }

  readonly #bytes: Uint8Array;

  constructor(bytes: Uint8Array) {
    invariant(bytes.length === Address.LENGTH, `Expected ${Address.LENGTH} bytes, got ${bytes.length}`);
    this.#bytes = bytes;
  }

  equals(other: Address): boolean {
    return this.#bytes.every((b, i) => b === other.#bytes[i]);
  }

  toBytes(): Uint8Array {
    return new Uint8Array(this.#bytes);
  }

  toHex(): string {
    return '0x' + Array.from(this.#bytes, b => b.toString(16).padStart(2, '0')).join('');
  }

  toString(): string {
    return this.toHex();
  }

  toJSON(): string {
    return this.toHex();
  }
}

Import Order

// 1. Node builtins
import { readFileSync } from 'fs';

// 2. External packages (alphabetized)
import axios from 'axios';
import invariant from 'tiny-invariant';

// 3. Internal imports (alphabetized)
import { API_URL } from './constants';
import type { User } from './types';

Recommended Packages

Package Purpose
tiny-invariant Runtime assertions
zod Schema validation
ky HTTP client
vitest Testing

Rust

Commands

cargo build --release    # Optimized build
cargo build              # Debug build
cargo test               # Run tests
cargo clippy -- -D warnings  # Lint
cargo fmt --check        # Check format
cargo fmt                # Format

File Structure

src/
├── main.rs          # Entry point
├── lib.rs           # Public exports
├── error.rs         # Error types
├── handlers/        # HTTP handlers
├── services/        # Business logic
├── types/           # Domain types
└── db/              # Database layer

Error Handling

use thiserror::Error;

#[derive(Debug, Error)]
pub enum AppError {
    #[error("User {0} not found")]
    UserNotFound(String),

    #[error("Invalid {field}: {reason}")]
    Validation { field: &'static str, reason: String },

    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),
}

impl AppError {
    pub fn validation(field: &'static str, reason: impl Into<String>) -> Self {
        Self::Validation { field, reason: reason.into() }
    }
}

Handler Pattern

use axum::{extract::State, Json};
use std::sync::Arc;

pub async fn get_user(
    State(state): State<Arc<AppState>>,
    Path(user_id): Path<String>,
) -> Result<Json<UserResponse>, AppError> {
    let user = state.user_service.find(&user_id).await?;
    Ok(Json(user.into()))
}

Struct Conventions

// Request: Deserialize only
#[derive(Deserialize)]
pub struct CreateUserRequest {
    pub name: String,
    pub email: String,
}

// Response: Serialize only
#[derive(Serialize)]
pub struct UserResponse {
    pub id: String,
    pub name: String,
}

// Internal: derive as needed
#[derive(Debug, Clone)]
pub struct User {
    pub id: String,
    pub name: String,
}

impl From<User> for UserResponse {
    fn from(u: User) -> Self {
        Self { id: u.id, name: u.name }
    }
}

Import Order

// 1. Standard library
use std::sync::Arc;

// 2. External crates (alphabetized)
use axum::Json;
use serde::Deserialize;

// 3. Crate imports
use crate::types::User;
use crate::AppError;

Recommended Crates

Purpose Crate
Async runtime tokio
Web framework axum
Serialization serde, serde_json
Error handling thiserror, anyhow
HTTP client reqwest
Database sqlx
Logging tracing

Sui Move

Remove this section if not using Sui Move.

Commands

sui move build                              # Compile
sui move test                               # Run tests
sui move build --skip-fetch-latest-git-deps # Faster rebuild

Implicit Imports (Sui 1.45+)

These are available without import:

  • sui:: (object, transfer, tx_context, coin, etc.)
  • std:: (vector, string, option, etc.)

Module Structure

module package::module_name;

// === Imports ===

// === Constants ===

// === Errors ===

// === Structs ===
// Order: OTW → Witness → Cap → Key → Owned → Shared → Data → Event

// === Public Functions ===

// === Package Functions ===

// === Private Functions ===

// === Test Functions ===

Struct Patterns

// One-time witness: ALL_CAPS, matches module
public struct MODULE_NAME has drop {}

// Capability: ends with Cap
public struct AdminCap has key, store { id: UID }

// Shared object: has key
public struct Registry has key {
    id: UID,
    count: u64,
}

// Dynamic field key: positional with Key suffix
public struct ItemKey(ID) has copy, drop, store;

// Event: past tense
public struct ItemCreated has copy, drop {
    id: ID,
    creator: address,
}

Function Conventions

// Parameter order:
// 1. &mut self (primary object)
// 2. &self
// 3. Shared objects (mut before immut)
// 4. Owned objects and capabilities
// 5. Pure values
// 6. TxContext (always last)

public fun create_item(
    registry: &mut Registry,  // 1. mutable self
    cap: &AdminCap,           // 4. capability
    name: vector<u8>,         // 5. pure value
    ctx: &mut TxContext,      // 6. context last
): ID {
    // ...
}

Syntax Preferences

// ✓ Dot syntax
self.id.delete();
ctx.sender();
vec[0];
b"hello".to_string();

// ✗ Module function syntax
object::delete(self.id);
tx_context::sender(ctx);
vector::borrow(&vec, 0);

Dynamic Fields

// Adding
df::add(&mut obj.id, ItemKey(item_id), item_data);

// Reading
let data: &ItemData = df::borrow(&obj.id, ItemKey(item_id));

// Checking existence
if (df::exists_(&obj.id, ItemKey(item_id))) {
    // ...
}

// Removing (required before object deletion)
let data: ItemData = df::remove(&mut obj.id, ItemKey(item_id));

Test Pattern

#[test]
fun test_create_item_succeeds() {
    let mut ctx = tx_context::dummy();
    let mut registry = create_registry_for_testing(&mut ctx);

    let id = create_item(&mut registry, b"test", &mut ctx);

    assert!(registry.count == 1);
    destroy_registry_for_testing(registry);
}

#[test]
#[expected_failure(abort_code = ENotAuthorized)]
fun test_create_item_unauthorized() {
    // ...
}

Git Conventions

Commit Format

emoji type(scope): subject

Types

Emoji Type Use For
feat New feature
🐛 fix Bug fix
📝 docs Documentation
♻️ refactor Code restructure
perf Performance
test Tests
📦 build Build/dependencies
🔧 chore Maintenance

Examples

✨ feat(auth): add OAuth2 login
🐛 fix(api): handle null response
♻️ refactor(db): extract connection pool
📦 build(deps): update axios to 1.7.0

Rules

  • Lowercase subject
  • No period at end
  • One logical change per commit

Pre-Commit Checklist

TypeScript

  • pnpm build passes
  • pnpm lint passes (zero warnings)
  • pnpm test passes
  • No any or @ts-ignore

Rust

  • cargo build passes
  • cargo clippy -- -D warnings passes
  • cargo fmt --check passes
  • cargo test passes
  • No .unwrap() in production

Move

  • sui move build passes
  • sui move test passes
  • Correct parameter order on functions

All

  • Commit message follows format
  • Single logical change
  • No secrets committed

Project Configuration

Fill in after project setup.

Environment Variables

# Required
API_URL=
DATABASE_URL=

# Optional
LOG_LEVEL=info

Deployed Addresses

Network Package ID Key Objects
Mainnet 0x... 0x...
Testnet 0x... 0x...

Resources

Resource URL
API Docs
Dashboard
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment