Instructions for AI assistants working on this codebase.
- Replace
[PROJECT_NAME]and[DESCRIPTION]with your project details - Remove language sections you don't use
- Fill in the Project Configuration section at the end
- Delete this "How to Use" section after customizing
[PROJECT_NAME]: [DESCRIPTION]
Directory Structure:
project/
├── contracts/ # Smart contracts
├── sdk/ # TypeScript SDK
├── server/ # Backend server
├── scripts/ # Tooling and deployment
└── docs/ # Documentation
How to approach tasks in this codebase.
- Understand first - Read relevant files before making changes
- Verify assumptions - Check existing patterns in the codebase
- Incremental changes - Small, testable changes over large refactors
- Validate after changes - Run build and tests before completing
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
- Minimize blast radius - Touch only what's necessary
- Preserve patterns - Match existing code style and architecture
- No surprise dependencies - Discuss before adding new packages
- Single responsibility - One logical change per commit
These rules apply to ALL code in this project.
- No comments - Code must be self-documenting through clear naming
- No TODOs/FIXMEs - Complete implementations before committing
- No dead code - Delete unused code; git has history
- No magic values - Use named constants
- No ignored errors - Handle or propagate every error
- No re-exports - Use
exportdirectly on declarations
Write optimal code on the first attempt. These patterns are non-negotiable.
-
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);
-
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);
-
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();
-
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);
-
Uint8Array for binary -
number[]only at serialization boundaries// ✓ Internal binary storage readonly #bytes: Uint8Array; // ✗ Array for internal binary readonly #bytes: number[];
-
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');
-
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 };
-
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');
| 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 |
- Validate inputs - At system boundaries
- No hardcoded secrets - Use environment variables
- Defensive copies - Return
new Uint8Array(this.#bytes), notthis.#bytes - Bounds checking - Use
bigintfor large numbers, validate ranges - Parameterized queries - Never string concatenation for SQL
- Sanitize output - Escape user input before rendering
Patterns to actively avoid.
- ❌ Catching errors without handling or re-throwing
- ❌ Mutable state in module scope
- ❌
console.login production (use structured logging) - ❌ Mixing concerns in single functions
- ❌
as anyor@ts-ignoreto bypass type errors - ❌
export { x }at end of file (export inline instead) - ❌ Optional chaining without handling undefined case
- ❌ Non-null assertion
!without invariant
- ❌
.unwrap()outside tests - ❌
.clone()to satisfy borrow checker without understanding - ❌
asyncon functions that don't await - ❌
impl Traitreturn when concrete type is clearer
- ❌
transfer::public_transferfor objects that should be shared - ❌ Large vectors in objects (use dynamic fields)
- ❌ Missing
ctx: &mut TxContext(breaks upgrades) - ❌
object::deleteon objects with dynamic fields (leaks storage)
- One assertion per test - Multiple focused tests over one large test
- Descriptive names -
test_withdraw_fails_when_balance_insufficient - AAA pattern - Arrange, Act, Assert
- Test behavior, not implementation - Focus on inputs and outputs
| Priority | What |
|---|---|
| Always | Public APIs, edge cases, error conditions |
| Usually | Complex private helpers, state transitions |
| Never | Trivial getters, framework internals |
__tests__/
├── unit/ # Isolated function tests
├── integration/ # Cross-module tests
└── e2e/ # Full system tests
- Check if functionality exists in current deps
- Prefer well-maintained, widely-used packages
- Pin exact versions in production
- Document why each dependency was added
// package.json - exact versions
"dependencies": {
"axios": "1.6.0"
}# Cargo.toml - exact versions
[dependencies]
tokio = "=1.35.0"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 onlysrc/
├── index.ts # Public exports
├── client.ts # Main implementation
├── client.types.ts # Types and interfaces
├── constants.ts # Constants
├── utils/ # Utilities
└── __tests__/ # Tests
| 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 |
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());
}
}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();
}
}// 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';| Package | Purpose |
|---|---|
tiny-invariant |
Runtime assertions |
zod |
Schema validation |
ky |
HTTP client |
vitest |
Testing |
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 # Formatsrc/
├── main.rs # Entry point
├── lib.rs # Public exports
├── error.rs # Error types
├── handlers/ # HTTP handlers
├── services/ # Business logic
├── types/ # Domain types
└── db/ # Database layer
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() }
}
}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()))
}// 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 }
}
}// 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;| Purpose | Crate |
|---|---|
| Async runtime | tokio |
| Web framework | axum |
| Serialization | serde, serde_json |
| Error handling | thiserror, anyhow |
| HTTP client | reqwest |
| Database | sqlx |
| Logging | tracing |
Remove this section if not using Sui Move.
sui move build # Compile
sui move test # Run tests
sui move build --skip-fetch-latest-git-deps # Faster rebuildThese are available without import:
sui::(object, transfer, tx_context, coin, etc.)std::(vector, string, option, etc.)
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 ===// 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,
}// 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 {
// ...
}// ✓ 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);// 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]
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() {
// ...
}emoji type(scope): subject
| Emoji | Type | Use For |
|---|---|---|
| ✨ | feat | New feature |
| 🐛 | fix | Bug fix |
| 📝 | docs | Documentation |
| ♻️ | refactor | Code restructure |
| ⚡ | perf | Performance |
| ✅ | test | Tests |
| 📦 | build | Build/dependencies |
| 🔧 | chore | Maintenance |
✨ feat(auth): add OAuth2 login
🐛 fix(api): handle null response
♻️ refactor(db): extract connection pool
📦 build(deps): update axios to 1.7.0
- Lowercase subject
- No period at end
- One logical change per commit
-
pnpm buildpasses -
pnpm lintpasses (zero warnings) -
pnpm testpasses - No
anyor@ts-ignore
-
cargo buildpasses -
cargo clippy -- -D warningspasses -
cargo fmt --checkpasses -
cargo testpasses - No
.unwrap()in production
-
sui move buildpasses -
sui move testpasses - Correct parameter order on functions
- Commit message follows format
- Single logical change
- No secrets committed
Fill in after project setup.
# Required
API_URL=
DATABASE_URL=
# Optional
LOG_LEVEL=info| Network | Package ID | Key Objects |
|---|---|---|
| Mainnet | 0x... |
0x... |
| Testnet | 0x... |
0x... |
| Resource | URL |
|---|---|
| API Docs | |
| Dashboard |