Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .claude/hooks/ts_lint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env python3

import json
import sys
import subprocess
from pathlib import Path


def main():
try:
# Read input data from stdin
input_data = json.load(sys.stdin)

tool_input = input_data.get("tool_input", {})
print(tool_input)

# Get file path from tool input
file_path = tool_input.get("file_path")
if not file_path:
sys.exit(0)

# Only check TypeScript/JavaScript files
if not file_path.endswith((".ts", ".tsx", ".js", ".jsx")):
sys.exit(0)

# Check if file exists
if not Path(file_path).exists():
sys.exit(0)

# Run ESLint to check for errors and style violations
try:
result = subprocess.run(
["npx", "eslint", file_path, "--format", "compact"],
capture_output=True,
text=True,
timeout=30,
)

if result.returncode != 0 and (result.stdout or result.stderr):
# Log the error for debugging
log_file = Path(__file__).parent.parent / "eslint_errors.json"
error_output = result.stdout or result.stderr
error_entry = {
"file_path": file_path,
"errors": error_output,
"session_id": input_data.get("session_id"),
}

# Load existing errors or create new list
if log_file.exists():
with open(log_file, "r") as f:
errors = json.load(f)
else:
errors = []

errors.append(error_entry)

# Save errors
with open(log_file, "w") as f:
json.dump(errors, f, indent=2)

# Send error message to stderr for LLM to see
print(f"ESLint errors found in {file_path}:", file=sys.stderr)
print(error_output, file=sys.stderr)

# Exit with code 2 to signal LLM to correct
sys.exit(2)

except subprocess.TimeoutExpired:
print("ESLint check timed out", file=sys.stderr)
sys.exit(0)
except FileNotFoundError:
# ESLint not available, skip check
sys.exit(0)

except json.JSONDecodeError as e:
print(f"Error parsing JSON input: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error in eslint hook: {e}", file=sys.stderr)
sys.exit(1)


main()
79 changes: 79 additions & 0 deletions .claude/hooks/use_yarn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
import json
import sys
import re
from pathlib import Path

def main():
try:
# Read input data from stdin
input_data = json.load(sys.stdin)
# tool_name = input_data.get("tool_name")
tool_input = input_data.get("tool_input", {})
command = tool_input.get("command", "")

if not command:
sys.exit(0)

# Check for npm, bun, pnpm commands and npx/bunx commands
npm_pattern = r"\bnpm\s+"
npx_pattern = r"\bnpx\s+"
bun_pattern = r"\bbun\s+"
bunx_pattern = r"\bbunx\s+"
pnpm_pattern = r"\bpnpm\s+"

blocked_command = None
suggested_command = None

if re.search(npm_pattern, command):
blocked_command = command
suggested_command = re.sub(r"\bnpm\b", "yarn", command)
elif re.search(npx_pattern, command):
blocked_command = command
suggested_command = re.sub(r"\bnpx\b", "yarn dlx", command)
elif re.search(bun_pattern, command):
blocked_command = command
suggested_command = re.sub(r"\bbun\b", "yarn", command)
elif re.search(bunx_pattern, command):
blocked_command = command
suggested_command = re.sub(r"\bbunx\b", "yarn dlx", command)
elif re.search(pnpm_pattern, command):
blocked_command = command
suggested_command = re.sub(r"\bpnpm\b", "yarn", command)

if blocked_command:
# Log the usage attempt
log_file = Path(__file__).parent.parent / "yarn_enforcement.json"
log_entry = {
"session_id": input_data.get("session_id"),
"blocked_command": blocked_command,
"suggested_command": suggested_command,
}

# Load existing logs or create new list
if log_file.exists():
with open(log_file, "r") as f:
logs = json.load(f)
else:
logs = []

logs.append(log_entry)

# Save logs
with open(log_file, "w") as f:
json.dump(logs, f, indent=2)

# Send error message to stderr for LLM to see
print("Error: Use 'yarn/yarn dlx' instead of 'npm/npx/bun/bunx/pnpm'", file=sys.stderr)

# Exit with code 2 to signal LLM to correct
sys.exit(2)

except json.JSONDecodeError as e:
print(f"Error parsing JSON input: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error in use-yarn hook: {e}", file=sys.stderr)
sys.exit(1)

main()
48 changes: 48 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"permissions": {
"allow": [
"Bash(yarn)",
"Bash(yarn add:*)",
"Bash(yarn install:*)",
"Bash(yarn config:*)",
"Bash(yarn run lint)",
"Bash(yarn run:*)",
"Bash(yarn dlx:*)"
],
"deny": [
"Read(.env)",
"Read(**/.env*)",
"Read(**/env*)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Read(**/*.crt)",
"Read(**/*.cert)",
"Read(**/secrets/**)",
"Read(**/credentials/**)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "uv run ~/.claude/hooks/use_yarn.py"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "uv run ~/.claude/hooks/ts_lint.py"
}
]
}
]
}
}
59 changes: 59 additions & 0 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Claude PR Assistant

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]

jobs:
claude-code-action:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && contains(github.event.issue.body, '@claude'))
runs-on: ubuntu-latest
permissions:
# TODO: change to write when team feels ready enough to handle creation of PR's directly from Claude
contents: read
pull-requests: read
issues: read
id-token: write
# contents: write
# pull-requests: write
# issues: write
# id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Run Claude PR Action
uses: anthropics/claude-code-action@beta
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
# Or use OAuth token instead:
# claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
timeout_minutes: '60'
allowed_tools: |
mcp__github__create_pull_request

custom_instructions: |
You MUST open a draft pull request after creating a branch.
You MUST create a pull request after completing your task.
You can create pull requests using the `mcp__github__create_pull_request` tool.
# Optional: Restrict network access to specific domains only
# experimental_allowed_domains: |
# .anthropic.com
# .github.com
# api.github.com
# .githubusercontent.com
# bun.sh
# registry.npmjs.org
# .blob.core.windows.net
93 changes: 93 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

### Development

- `yarn dev` - Start development server (automatically runs `build:metadata` first)
- `yarn` - Install dependencies
- `yarn build` - Build production version (runs `build:metadata` first)
- `yarn start` - Start production server

### Code Quality

- `yarn lint` - Run ESLint
- `yarn format` - Format code with Prettier
- `yarn ts` - Run TypeScript type checking without emitting files

### Utility

- `yarn build:metadata` - Pull metadata from external sources (required before dev/build)
- `yarn analyze` - Build with bundle analyzer
- `yarn convert-spec` - Convert API specification files

## Architecture

### Technology Stack

- **Framework**: Next.js 15 (App Router not used - pages directory structure)
- **React**: 19.1.0 with TypeScript
- **Styling**: Tailwind CSS 4.x with PostCSS
- **State Management**: React Query (@tanstack/react-query) for server state
- **Charts**: ECharts library for data visualization
- **Authentication**: PocketBase for auth with custom AuthProvider
- **Web3**: Wagmi + Viem for blockchain interactions
- **Search**: Meilisearch with React InstantSearch

### Project Structure

- `src/pages/` - Next.js pages (not App Router)
- `src/components/` - Reusable UI components
- `src/containers/` - Page-specific container components
- `src/api/` - API utilities and client functions
- `src/hooks/` - Custom React hooks
- `src/utils/` - Utility functions
- `src/layout/` - Layout components
- `src/constants/` - Application constants
- `src/contexts/` - React contexts
- `public/` - Static assets
- `scripts/` - Build and deployment scripts

### Key Architectural Patterns

- **Component Organization**: Components are organized by feature/domain in containers, with shared components in the components directory
- **Data Fetching**: Uses React Query for server state management with custom hooks
- **Styling**: Tailwind CSS with custom utility classes and CSS variables for theming
- **Charts**: ECharts wrapper components with TypeScript interfaces for props
- **Routing**: Next.js file-based routing with extensive URL redirects in next.config.js
- **Type Safety**: TypeScript with interfaces defined in types.ts files throughout the codebase

### Configuration

- TypeScript config disables strict mode but enables forceConsistentCasingInFileNames
- Path aliases: `~/*` maps to `./src/*` and `~/public/*` maps to `./public/*`
- Static page generation timeout increased to 5 minutes for large datasets
- Image optimization configured for external domains (icons.llama.fi, etc.)

### Data Flow

- External APIs provide DeFi protocol data and metrics
- Metadata is pulled during build process via `scripts/pullMetadata.js`
- React Query manages caching and synchronization of server state
- Charts consume processed data through ECharts wrapper components
- Search functionality uses Meilisearch instance search

### Development Notes

- Development server requires metadata build step before starting
- Uses Redis (ioredis) for caching in production
- Extensive URL redirect configuration for legacy route compatibility
- Analytics integration with Fathom
- Bundle analysis available via `yarn analyze`

## Coding Style Guidelines

- **File Naming**: Use PascalCase for React components (`ChainOverview.tsx`), camelCase for utilities (`useAnalytics.tsx`), lowercase-with-dashes for directories (`chain-overview/`)
- **TypeScript**: Use interfaces with `I` prefix for props (`IChartProps`), avoid `any` type, use named exports for components (default exports only for pages)
- **Imports**: Use path aliases `~/` for src imports, group imports by: external libraries, internal components, utilities, types
- **Components**: Use React.memo() for performance-critical components, functional components with hooks, place logic (state, effects) at top of function
- **Styling**: Use Tailwind CSS with CSS custom properties for theming (`--cards-bg`, `--text1`), use data attributes for conditional styling (`data-[align=center]:justify-center`)
- **Data Management**: Use React Query for server state, utility functions from `~/utils` for formatting (`formattedNum()`, `formattedPercent()`), proper useEffect cleanup
- **Performance**: Use virtualization for large lists (@tanstack/react-virtual), React.lazy() for code splitting, useCallback/useMemo for optimization