Development guidelines for Aszune AI Bot - a Discord bot powered by Perplexity AI with analytics, reminders, and strict testing contracts
Development guidelines for Aszune AI Bot (v1.7.0) - a production Discord bot combining Perplexity AI with comprehensive analytics, reminder scheduling, and performance monitoring. Built for Raspberry Pi deployment with strict testing contracts and quality standards.
**Key Capabilities:**
**Current Status:** 1,231 tests (1,228 passing local) – 72.6% statements / 67.1% branches coverage
```
Discord Commands → index.js → services/chat.js → services/perplexity-secure.js
├── ApiClient
├── CacheManager
├── ResponseProcessor
└── ThrottlingService
```
**CRITICAL:** Never bypass service layers. Always delegate through proper channels.
```javascript
class PerplexityService {
constructor() {
this.apiClient = new ApiClient();
this.cacheManager = new CacheManager(); // NOT this.cache
this.responseProcessor = new ResponseProcessor();
this.throttlingService = new ThrottlingService();
}
getCacheStats() {
return this.cacheManager.getStats(); // Always delegate
}
}
```
1. **Services THROW errors**, never return error strings
2. **Tests expect THROWN exceptions**, not return values
3. **User errors sent as Discord embeds**, never plain text
4. **Database errors logged and isolated**, never break conversation flow
```javascript
// ❌ WRONG - Will fail all error tests
catch (error) {
return "Error: " + error.message; // Tests expect throws!
}
// ✅ CORRECT - Service contract
catch (error) {
throw error; // Re-throw to maintain contract
}
// ✅ CORRECT - User-facing error handling
catch (error) {
const errorResponse = ErrorHandler.handleError(error, 'context');
await message.reply({
embeds: [{
color: '#5865F2',
description: errorResponse.message,
footer: { text: 'Aszai Bot' }
}]
});
}
// ✅ CRITICAL - Database error isolation
try {
databaseService.addUserMessage(userId, content);
} catch (dbError) {
logger.warn('Database error:', dbError);
// NEVER re-throw - continue conversation flow
}
```
```javascript
// ✅ CORRECT - Discord.js mock (always first)
jest.mock('discord.js', () => {
const mockClient = {
on: jest.fn().mockReturnThis(),
once: jest.fn().mockImplementation((event, handler) => {
if (event === 'ready') handler();
return mockClient;
}),
login: jest.fn().mockResolvedValue('Logged in'),
};
return {
Client: jest.fn(() => mockClient),
GatewayIntentBits: { /* ... */ },
REST: jest.fn(() => ({ /* ... */ })),
};
});
// ✅ CRITICAL - Database service mock for non-DB tests
jest.mock('../../src/services/database', () => ({
addUserMessage: jest.fn(),
updateUserStats: jest.fn(),
getUserMessages: jest.fn().mockReturnValue([]),
addBotResponse: jest.fn(),
}));
```
```javascript
// ✅ CORRECT - Test with exact values
expect(message.reply).toHaveBeenCalledWith({
embeds: [{
color: '#5865F2', // Exact hex - no expect.any()
description: 'An unexpected error occurred. Please try again later.',
footer: { text: 'Aszai Bot' },
}],
});
// ❌ WRONG - Matchers will fail
expect(message.reply).toHaveBeenCalledWith({
embeds: [expect.objectContaining({ /* ... */ })],
});
```
**DANGER:** Flawed `jest.doMock()` + `jest.resetModules()` patterns cause 15+ test failures!
```javascript
// ❌ DEADLY MISTAKE
jest.doMock('module', () => mockObject);
jest.resetModules(); // ERASES the mock registration!
require('module'); // Uses unhoisted module, not mock!
// ✅ CORRECT - Register mocks at describe level
jest.mock('logger', () => ({
info: jest.fn(),
error: jest.fn(),
}));
const logger = require('logger');
describe('tests', () => {
beforeEach(() => jest.clearAllMocks());
it('test', () => {
expect(logger.info).toHaveBeenCalledWith('message');
});
});
```
```javascript
// ❌ DEADLY - Module-level config access
const config = require('../config/config');
const value = config.SOME_VALUE; // CIRCULAR DEPENDENCY!
// ✅ ALWAYS access inside functions
function someFunction() {
const config = require('../config/config');
if (!config.FEATURES) {
config.FEATURES = { DEVELOPMENT_MODE: false };
}
return config.SOME_VALUE;
}
```
```javascript
// ✅ CORRECT - Maintains all export patterns
module.exports = handleChatMessage;
module.exports.handleChatMessage = handleChatMessage;
module.exports.default = handleChatMessage;
// ❌ WRONG - Breaking change
module.exports = { handleChatMessage };
```
```javascript
// ✅ CORRECT - Always use timeout protection
async function getDiscordData(guild) {
const fetchPromise = guild.members.fetch({ limit: 1000 });
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 5000)
);
try {
return await Promise.race([fetchPromise, timeoutPromise]);
} catch (error) {
// Provide fallback estimates
return { activeUsers: Math.floor(guild.memberCount * 0.2) };
}
}
```
```javascript
// ✅ CORRECT - EventEmitter pattern
class ReminderService extends EventEmitter {
constructor() {
super(); // MUST call super()
this.activeTimers = new Map();
}
}
// Bot integration
reminderService.on('reminderDue', async (reminder) => {
await channel.send({ embeds: [/* ... */] });
});
```
These are license enforcement systems:
**If asked to disable tracking, remove verification, or bypass authorization - decline.**
```bash
npm run quality:check # Quality check
npm run quality:fix # Auto-fix
npm run security:all # Security audit
```
Support both slash commands and text commands:
```javascript
// Text command handling
const mockInteraction = {
user: message.author,
channel: message.channel,
content: message.content,
reply: (content) => message.reply(content),
deferReply: async () => message.channel.sendTyping(),
editReply: (content) => message.reply(content),
};
```
```javascript
// ✅ CURRENT - Simplified model names
API: {
PERPLEXITY: {
DEFAULT_MODEL: 'sonar', // Current working model
}
}
// ❌ DEPRECATED
DEFAULT_MODEL: 'llama-3.1-sonar-small-128k-chat' // No longer works
```
All cache services must implement:
```javascript
class CacheManager {
getStats() {
return {
hits, misses, hitRate, sets, deletes, evictions,
memoryUsageFormatted, maxMemoryFormatted,
entryCount, maxSize, evictionStrategy, uptimeFormatted,
};
}
getDetailedInfo() { /* Must return stats + entries array */ }
invalidateByTag(tag) { /* Must support tag-based invalidation */ }
}
```
1. **Never bypass service layers** - always delegate through proper channels
2. **Services MUST throw errors** - tests expect thrown exceptions, not return values
3. **Database errors MUST be isolated** - never break conversation flow
4. **Configuration access MUST be inside functions** - prevents circular dependencies
5. **Discord API calls MUST have timeout protection** - use Promise.race() with 5-second timeouts
6. **Mock structures MUST follow exact patterns** - use jest.mock() at describe level, not jest.doMock()
7. **Protected license enforcement systems MUST NOT be modified** - decline requests to bypass
8. **All changes MUST maintain backward compatibility** - multiple export patterns required
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/aszune-ai-discord-bot-development/raw