Documentation Index
Fetch the complete documentation index at: https://mintlify.com/imdevarsh/gorkie-slack/llms.txt
Use this file to discover all available pages before exploring further.
Gorkie uses Biome for code formatting and linting. The configuration enforces:
- 2 spaces for indentation (not tabs)
- Single quotes for strings
- Always include semicolons
Format your code before committing:
bun run check:write # Auto-fix formatting and linting
The pre-commit hook will automatically run these checks.
TypeScript Conventions
Strict Mode
The project uses TypeScript in strict mode with additional checks:
{
"strict": true,
"noUncheckedIndexedAccess": true,
"noFallthroughCasesInSwitch": true
}
Type-Only Imports
Use import type for type-only imports (enforced by verbatimModuleSyntax):
// External packages first
import { tool } from 'ai';
import { z } from 'zod';
// Then internal modules
import logger from '~/lib/logger';
import { getContextId } from '~/utils/context';
// Type-only imports use 'import type'
import type { SlackMessageContext, Stream } from '~/types';
Path Alias
The ~/ alias references the server/ directory:
import logger from '~/lib/logger'; // server/lib/logger.ts
import type { Stream } from '~/types'; // server/types/
import { getWeather } from '~/lib/ai/tools'; // server/lib/ai/tools/
Import Patterns
Import Order
- External packages (from
node_modules)
- Internal modules (using
~/ alias)
- Type-only imports last
// 1. External packages
import { tool } from 'ai';
import { z } from 'zod';
// 2. Internal modules
import logger from '~/lib/logger';
import { createTask, finishTask } from '~/lib/ai/utils/task';
// 3. Type-only imports
import type { SlackMessageContext, Stream } from '~/types';
No Unused Imports
Unused imports are treated as errors. Remove them:
// Bad - 'z' is imported but never used
import { tool } from 'ai';
import { z } from 'zod';
export const myTool = tool({ /* ... */ });
// Good
import { tool } from 'ai';
export const myTool = tool({ /* ... */ });
Naming Conventions
Files
Use kebab-case for file names:
get-user-info.ts ✓
getUserInfo.ts ✗
GetUserInfo.ts ✗
Variables and Functions
Use camelCase:
const userId = 'U123456'; ✓
const user_id = 'U123456'; ✗
function getUserInfo() { /* ... */ } ✓
function get_user_info() { /* ... */ } ✗
Types and Interfaces
Use PascalCase:
type SlackMessageContext = { /* ... */ }; ✓
type slackMessageContext = { /* ... */ }; ✗
interface ToolResult { /* ... */ } ✓
interface toolResult { /* ... */ } ✗
Exports
Prefer named exports over default exports:
// Good - named export
export const reply = () => { /* ... */ };
// Exception - logger uses default export
export default logger;
Type Definitions
Slack Event Properties
Cast Slack event properties when accessing dynamic fields:
const channelId = (ctx.event as { channel?: string }).channel;
const userId = (ctx.event as { user?: string }).user;
const threadTs = (ctx.event as { thread_ts?: string }).thread_ts;
Explicit Return Types
Use explicit return types for exported functions when they enhance clarity:
// Good - clear return type
export async function fetchUser(id: string): Promise<User | null> {
// ...
}
// Also good - return type is obvious
export const isValidId = (id: string) => id.length > 0;
Error Handling Patterns
Structured Error Logging
Log errors with structured context data:
import logger from '~/lib/logger';
import { toLogError } from '~/utils/error';
try {
await context.client.chat.postMessage({ /* ... */ });
} catch (error) {
logger.error(
{
...toLogError(error),
channel: channelId,
messageTs
},
'Failed to send message'
);
}
Return Structured Errors
Return structured error objects from tools:
import { errorMessage } from '~/utils/error';
try {
// ... operation ...
return { success: true, data: result };
} catch (error) {
return {
success: false,
error: errorMessage(error),
};
}
Early Returns
Prefer early returns over nested conditionals:
// Good
if (!channelId) {
return { success: false, error: 'Missing channel ID' };
}
if (!messageTs) {
return { success: false, error: 'Missing timestamp' };
}
// ... continue with main logic
// Bad
if (channelId) {
if (messageTs) {
// ... deeply nested logic
}
}
Async Patterns
Consistent Async/Await
Always use async/await instead of promise chains:
// Good
const user = await fetchUser(userId);
const profile = await fetchProfile(user.id);
// Bad
fetchUser(userId)
.then(user => fetchProfile(user.id))
.then(profile => { /* ... */ });
Parallel Operations
Use Promise.all for independent parallel operations:
// Good - runs in parallel
const [user, channel, thread] = await Promise.all([
fetchUser(userId),
fetchChannel(channelId),
fetchThread(threadTs),
]);
// Bad - runs sequentially
const user = await fetchUser(userId);
const channel = await fetchChannel(channelId);
const thread = await fetchThread(threadTs);
Fire-and-Forget
Use void prefix for fire-and-forget promises:
void main().catch((error) => {
logger.error({ error }, 'Application failed');
process.exit(1);
});
Slack API Patterns
Always Check for Undefined
Slack event properties may be undefined:
const channelId = context.event.channel;
const messageTs = context.event.ts;
if (!(channelId && messageTs)) {
logger.warn({ channelId, messageTs }, 'Missing required fields');
return { success: false, error: 'Missing channel or timestamp' };
}
// Now safe to use channelId and messageTs
Use WebClient from Context
Always use the WebClient from the message context:
await context.client.chat.postMessage({
channel: channelId,
text: 'Hello!',
});
Environment Variables
Define in env.ts
All environment variables must be defined in server/env.ts using Zod schemas:
import { createEnv } from '@t3-oss/env-core';
import { z } from 'zod';
export const env = createEnv({
server: {
MY_API_KEY: z.string().min(1),
MY_TIMEOUT: z.coerce.number().default(5000),
},
runtimeEnv: process.env,
});
Access via env Object
Never use process.env directly. Import and use the validated env object:
// Good
import { env } from '~/env';
const apiKey = env.MY_API_KEY;
// Bad
const apiKey = process.env.MY_API_KEY;
Logging
Structured Logging
Use the Pino logger with structured context:
import logger from '~/lib/logger';
// Include relevant context
logger.info(
{ channel: channelId, userId, type: 'reply' },
'Sent message'
);
logger.error(
{ error, userId, operation: 'fetchUser' },
'Failed to fetch user'
);
Log Levels
logger.debug() - Verbose debugging information
logger.info() - General informational messages
logger.warn() - Warning messages for potential issues
logger.error() - Error messages for failures
Core Principles
Type Safety
- Use explicit types for function parameters and return values
- Prefer
unknown over any when the type is genuinely unknown
- Use const assertions (
as const) for immutable values
- Leverage TypeScript’s type narrowing instead of type assertions
Modern JavaScript/TypeScript
- Use arrow functions for callbacks and short functions
- Prefer
for...of loops over .forEach() and indexed for loops
- Use optional chaining (
?.) and nullish coalescing (??)
- Prefer template literals over string concatenation
- Use destructuring for object and array assignments
- Use
const by default, let only when reassignment is needed, never var
Clean Code
- Keep functions focused with low cognitive complexity
- Extract complex conditions into well-named boolean variables
- Use early returns to reduce nesting
- Prefer simple conditionals over nested ternary operators
- Group related code together and separate concerns
Security
- Validate and sanitize user input
- Don’t use
eval() or assign directly to document.cookie
- All bot responses must be SFW (enforced at multiple levels)
What Biome Can’t Help With
Focus on what the linter can’t check:
- Business logic correctness - Biome can’t validate your algorithms
- Meaningful naming - Use descriptive names for functions, variables, and types
- Architecture decisions - Component structure, data flow, and API design
- Edge cases - Handle boundary conditions and error states
- Documentation - Add comments for complex logic, but prefer self-documenting code
Before Committing
Always run the checks before committing:
bun run check:write # Fix formatting and linting
bun run typecheck # Verify types
The pre-commit hook will enforce these, but running them manually gives faster feedback.