This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is a Slack bot for the Lullabot workspace built with TypeScript and the Slack Bolt framework. It uses a modular plugin architecture where each feature is implemented as a separate plugin that can be loaded dynamically.
# Development
npm run dev # Run in development mode with hot reload (nodemon + ts-node)
npm run build # Compile TypeScript to JavaScript
npm run watch # Watch mode compilation (tsc -w)
npm start # Production mode - builds and runs (node dist/bot.js)
# Testing
npm test # Run Jest tests with forceExit
npm test -- -t "Factoids Plugin" # Run specific test suite
# Utilities
npm run clean # Remove dist directory- Plugin Loading: Automatic discovery from
src/plugins/directory - Plugin Interface: Each plugin exports a default function
(app: App) => Promise<void> - Environment Aware: Loads
.tsfiles in development,.jsfiles in production - Error Handling: Individual plugin failures don't crash the entire bot
src/bot.ts: Main entry point with plugin loadersrc/plugins/: Individual feature modules (karma, factoids, help, etc.)src/services/pattern-registry.ts: Centralized pattern management with priority systemsrc/types/index.ts: TypeScript interfaces for Plugin, Storage, Command, HelpSectiondata/: JSON-based persistent storage (team-separated data files)
- factoids.ts: Store/retrieve Q&A responses with pattern matching
- karma.ts: User/item karma tracking with ++ and -- operators
- help.ts: Dynamic help system that documents all plugins
- hello.ts: Greeting detection and responses
- uptime.ts: Bot status and identification
- botsnack.ts: Fun interactions with thank you responses
import { App } from '@slack/bolt';
import { Plugin } from '../types';
const myPlugin: Plugin = async (app: App): Promise<void> => {
// Event handlers here
};
export default myPlugin;- Use the PatternRegistryService for conflict detection
- Higher priority patterns checked first
- Thread-aware responses (all plugins should respect threading)
- Proper TypeScript event typing with
GenericMessageEventandAppMentionEvent
- JSON files in
/datadirectory - Team-based file separation:
{teamId}-{pluginName}.json - Use Storage interface for consistent data handling
- Graceful file creation if data doesn't exist
- Jest Framework: Uses ts-jest preset with Node environment
- Test Location:
**/__tests__/**/*.test.tspattern - Factoid Tests: Comprehensive pattern matching validation in
src/plugins/__tests__/factoids.test.ts - Test Arrays:
shouldMatchPatternsandshouldNotMatchPatternsfor validation - Coverage: Automatic coverage collection with text and lcov reporters
Required environment variables:
BOT_TOKEN: Slack Bot User OAuth Token (xoxb-)SLACK_APP_TOKEN: App-level token (xapp-)CLIENT_SIGNING_SECRET: Slack app signing secretNODE_ENV: Set to 'production' for compiled JS, 'development' for ts-node
- Storage Format: JSON files per team per plugin
- Location:
/datadirectory (Docker volume mounted) - Team Isolation: Each Slack team gets separate data files
- Backup Strategy: File-based storage allows easy backup/restore
Bot uses Slack Socket Mode for real-time events:
- WebSocket connection for instant message delivery
- No webhook URLs needed
- Handles reconnection automatically
- Supports interactive elements (buttons, modals)
- Target: ES2020 with CommonJS modules
- Strict Mode: Full TypeScript strict checking enabled
- Slack Types: Uses official
@slack/typespackage - Module Resolution: Node-style module resolution
- Build Output:
dist/directory for production
To update the GitHub issue type field (not labels or project fields), use the GraphQL API:
gh api graphql -f query='
query {
repository(owner: "Lullabot", name: "lullabot-slackbot") {
issueTypes(first: 50) {
nodes {
id
name
}
}
}
}'Current issue types and their IDs:
- Task:
IT_kwDOAATm884AAZex - Bug:
IT_kwDOAATm884AAZe0 - Feature:
IT_kwDOAATm884AAZe2 - Spike:
IT_kwDOAATm884BgDJL - Epic:
IT_kwDOAATm884BgDJV
gh api graphql -f query='
query {
repository(owner: "Lullabot", name: "lullabot-slackbot") {
issue(number: ISSUE_NUMBER) {
id
issueType {
name
}
}
}
}'gh api graphql -f query='
mutation {
updateIssue(input: {id: "ISSUE_NODE_ID", issueTypeId: "TYPE_ID"}) {
issue {
number
issueType {
name
}
}
}
}'Example: Update issue #105 to Bug type:
gh api graphql -f query='
mutation {
updateIssue(input: {id: "I_kwDOH1dPoM7EVb98", issueTypeId: "IT_kwDOAATm884AAZe0"}) {
issue {
number
issueType {
name
}
}
}
}'Note: This is different from labels or project fields - it's GitHub's built-in issue type field.