CRITICAL: At the start of EVERY session, you MUST read the ARCHITECTURE.md file to understand the current application architecture and context.
- Always read
ARCHITECTURE.mdfirst to understand:- Current project structure
- Key modules and their responsibilities
- Data flow and dependencies
- Any architectural decisions or constraints
Whenever you make changes that affect the application architecture, you MUST update ARCHITECTURE.md to reflect:
- New files or modules - Add them to the appropriate section with a brief description
- New dependencies - Document any new packages added and their purpose
- Structural changes - Update the project structure if directories are added/removed
- New patterns or conventions - Document any new coding patterns introduced
- API changes - Update endpoint documentation if applicable
- Configuration changes - Note any new environment variables or config options
- Adding new source files or directories
- Creating new modules or services
- Adding external dependencies
- Changing the data flow between components
- Adding new API endpoints or routes
- Modifying the build or deployment configuration
- Adding new environment variables
Keep the ARCHITECTURE.md file:
- Concise but comprehensive
- Up-to-date with every architectural change
- Organized by logical sections
- Including file paths for easy reference
PRIORITY: Delegate investigation tasks to subagents to preserve main context capacity for problem-solving.
- Context preservation - The main conversation has limited context; investigations can consume significant capacity
- Focused problem-solving - Keep the main context clean for actual implementation and decision-making
- Parallel exploration - Multiple subagents can investigate different aspects simultaneously
- Better results - Subagents can dive deep without cluttering the main conversation
- Codebase exploration - Finding files, understanding structure, tracing dependencies
- Bug investigation - Searching for error sources, understanding call chains
- Research tasks - Looking up patterns, finding examples in the codebase
- Understanding existing code - Reading multiple files to understand how something works
- Finding all occurrences - Searching for usages of a function, type, or pattern
- Simple, single-file reads
- Direct questions with known file locations
- Tasks that require immediate user interaction
- Final implementation after investigation is complete
- Be specific - Give clear, focused instructions to the subagent
- Request summaries - Ask subagents to return concise findings, not raw data
- Use parallel agents - Launch multiple subagents for independent investigations
- Act on results - Use subagent findings for implementation in the main context
User: "Fix the bug where market prices aren't updating"
Good approach:
1. Launch subagent: "Find all files that handle market price updates and identify the data flow"
2. Receive summary of relevant files and flow
3. Use main context capacity for analyzing the bug and implementing the fix
Bad approach:
1. Manually search through 20+ files in main context
2. Read multiple large files trying to understand the flow
3. Run out of context before implementing the fix
PRIORITY: Always prefer library-exported types over custom type definitions.
- Check library types first - Before defining a custom interface or type, check if the library (e.g.,
@polymarket/clob-client) already exports it - Use library types when available - Import and use types directly from the library (e.g.,
Token,Chain,PaginationPayload) - Extend library types when partial - If the library type is incomplete, extend it rather than redefining (e.g.,
interface MarketsResponse extends Omit<PaginationPayload, "data">) - Create custom types only when necessary - Only define custom types when the library doesn't export them at all
- Abstract custom types for reuse - Place custom types in
src/types/for reusability across scripts
- Reusable types:
src/types/*.ts- Types used across multiple files - Script-specific types: Only if truly single-use and simple
- Add a comment explaining why a custom type was needed (e.g., "Not exported by @polymarket/clob-client")
PRIORITY: Always abstract reusable code and follow the DRY (Don't Repeat Yourself) principle.
- Search existing utilities first - Before writing new functionality, check if similar utilities already exist in:
src/utils/- Shared utility functionssrc/config/- Configuration constantssrc/types/- Type definitions
- Check library exports - The library may already provide the functionality you need
- Reuse existing code - Import and use existing utilities rather than duplicating logic
- Abstract configuration - Move hardcoded values (URLs, chains, constants) to
src/config/ - Abstract utilities - Extract reusable functions to
src/utils/:- Client factories (e.g.,
createClobClient()) - Formatters (e.g.,
formatMarket()) - Helpers and common operations
- Client factories (e.g.,
- Abstract types - Place reusable types in
src/types/ - Keep scripts thin - Scripts in
src/scripts/should primarily orchestrate utilities, not contain business logic
- Single Responsibility: Each utility should do one thing well
- Meaningful Names: Function and file names should clearly describe their purpose
- Documentation: Add JSDoc comments for exported functions
- Testability: Abstracted code is easier to test in isolation
PRIORITY: Scripts in src/scripts/ should be thin orchestration layers only.
- Import statements for utilities
- Configuration/setup (creating clients, parsing args)
- Orchestrating utility calls
- Console output for user feedback
- Error handling at the top level
- Business logic (move to
src/utils/) - Type definitions (move to
src/types/) - Data transformation functions (move to
src/utils/formatters.ts) - API fetching logic (move to
src/utils/gamma.tsor dedicated util) - Helper functions like
sleep(),log()(usesrc/utils/helpers.ts)
- Ideal: < 100 lines
- Maximum: ~150 lines
- If a script exceeds 150 lines, refactor logic into utilities
PRIORITY: Never use fetch() directly in script files.
- Direct fetch calls are hard to test (can't mock)
- Duplicates error handling logic
- Makes scripts harder to maintain
- Create a utility function in
src/utils/(e.g.,gamma.ts,orderbook.ts) - Accept an optional
fetcherparameter for testability - Import and use the utility in your script
// In src/utils/gamma.ts
export async function fetchMarketData(
slug: string,
fetcher: typeof fetch = fetch // Allows mocking in tests
): Promise<MarketData> {
const response = await fetcher(`${GAMMA_API_URL}/markets/${slug}`);
return response.json();
}
// In src/scripts/getMarket.ts
import { fetchMarketData } from "../utils/gamma";
const data = await fetchMarketData(slug);PRIORITY: Core utilities should accept dependencies as optional parameters.
- Functions that make HTTP requests
- Functions that use environment variables
- Functions that create clients with external dependencies
// Good: Accepts optional config with defaults
export function createClobClient(options?: ClobClientOptions): ClobClient {
return new ClobClient(
options?.host ?? CLOB_API_URL,
options?.chain ?? Chain.POLYGON,
// ...
);
}
// Good: Accepts optional fetcher for testing
export async function fetchEventBySlug(
slug: string,
fetcher: typeof fetch = fetch
): Promise<GammaEvent | null> {
// ...
}- Always provide sensible defaults (from
src/config/orsrc/utils/env.ts) - Maintain backward compatibility - existing code shouldn't break
When you see any of these patterns, extract to a utility:
| Pattern | Extract To |
|---|---|
await sleep(ms) or setTimeout wrapper |
src/utils/helpers.ts |
| Timestamp formatting | src/utils/helpers.ts |
| Console logging with prefixes | src/utils/helpers.ts |
fetch() calls to external APIs |
src/utils/gamma.ts or dedicated util |
Market data access (.outcomes, .outcomePrices) |
src/utils/markets.ts |
| Order book fetching/formatting | src/utils/orderbook.ts |
| Reward calculations | src/utils/rewards.ts |
| Price/currency/percent formatting | src/utils/formatters.ts |
| Client creation | src/utils/client.ts or authClient.ts |
- Pure functions - Functions that don't depend on external state are easiest to test
- Dependency injection - Pass dependencies as parameters with defaults
- Small, focused functions - Each function should do one thing
- Separate I/O from logic - Keep data fetching separate from data processing
// Test file example
import { fetchEventBySlug } from "../utils/gamma";
const mockFetcher = jest.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ /* mock data */ })
});
const result = await fetchEventBySlug("test-event", mockFetcher);
expect(mockFetcher).toHaveBeenCalledWith(expect.stringContaining("test-event"));- All functions in
src/utils/ - Strategy logic in
src/strategies/ - Type guards and validators