Skip to main content
Glama
hamster-rules-distribution.test.js6.58 kB
import { execSync } from 'child_process'; import fs from 'fs'; import os from 'os'; import path from 'path'; import { RULE_PROFILES } from '../../../src/constants/profiles.js'; import * as profilesModule from '../../../src/profiles/index.js'; /** * Integration tests for hamster rules distribution across all profiles. * * These tests verify that hamster.mdc is correctly distributed * to all profiles that include default rules when running `rules add --mode=team`. * Note: hamster.mdc is team-mode only (for Hamster API integration). */ describe('Hamster Rules Distribution', () => { const CLI_PATH = path.join(process.cwd(), 'dist', 'task-master.js'); // Profiles that have includeDefaultRules: false and won't get hamster files via defaultFileMap const PROFILES_WITHOUT_DEFAULT_RULES = [ 'claude', 'gemini', 'codex', 'amp', 'opencode', 'zed' ]; // Profiles that should receive hamster rules const PROFILES_WITH_DEFAULT_RULES = RULE_PROFILES.filter( (p) => !PROFILES_WITHOUT_DEFAULT_RULES.includes(p) ); // Get expected hamster file path by reading from the actual profile object const getExpectedHamsterPath = (profileName, tempDir) => { const profile = profilesModule[`${profileName}Profile`]; if (!profile || !profile.fileMap) return null; const rulesDir = profile.rulesDir; const hamsterTarget = profile.fileMap['rules/hamster.mdc']; if (!hamsterTarget) return null; return path.join(tempDir, rulesDir, hamsterTarget); }; describe('Source files exist', () => { test('hamster.mdc exists in assets/rules', () => { const hamsterPath = path.join( process.cwd(), 'assets', 'rules', 'hamster.mdc' ); expect(fs.existsSync(hamsterPath)).toBe(true); }); test('hamster.mdc has correct frontmatter', () => { const hamsterPath = path.join( process.cwd(), 'assets', 'rules', 'hamster.mdc' ); const content = fs.readFileSync(hamsterPath, 'utf8'); expect(content).toContain('---'); expect(content).toContain('description:'); expect(content).toContain('globs:'); expect(content).toContain('alwaysApply:'); expect(content).toContain('Hamster Integration Workflow'); }); }); describe('Rules add command distributes hamster file in team mode', () => { // Test each profile that should receive hamster rules when --mode=team PROFILES_WITH_DEFAULT_RULES.forEach((profile) => { test(`${profile} profile receives hamster rules via 'rules add --mode=team'`, () => { // Create a unique temp directory for this test const tempDir = fs.mkdtempSync( path.join(os.tmpdir(), `tm-hamster-test-${profile}-`) ); try { // Run the rules add command with team mode (hamster.mdc is team-only) execSync(`node ${CLI_PATH} rules add ${profile} --mode=team`, { cwd: tempDir, stdio: 'pipe', env: { ...process.env, TASKMASTER_LOG_LEVEL: 'error' } }); // Get expected path for this profile const expectedPath = getExpectedHamsterPath(profile, tempDir); // Strictly enforce that all profiles with default rules must have hamster mapping expect(expectedPath).not.toBeNull(); // Verify hamster.* file exists expect(fs.existsSync(expectedPath)).toBe(true); // Verify hamster file contains expected content const hamsterContent = fs.readFileSync(expectedPath, 'utf8'); expect(hamsterContent).toContain('Hamster Integration Workflow'); expect(hamsterContent).toContain('tm list'); expect(hamsterContent).toContain('tm set-status'); } finally { // Cleanup temp directory fs.rmSync(tempDir, { recursive: true, force: true }); } }); }); }); describe('Solo mode excludes hamster file', () => { // Test that hamster.mdc is NOT distributed in solo mode PROFILES_WITH_DEFAULT_RULES.forEach((profile) => { test(`${profile} profile does NOT receive hamster rules via 'rules add --mode=solo'`, () => { const tempDir = fs.mkdtempSync( path.join(os.tmpdir(), `tm-hamster-solo-test-${profile}-`) ); try { // Run in solo mode - hamster should NOT be distributed execSync(`node ${CLI_PATH} rules add ${profile} --mode=solo`, { cwd: tempDir, stdio: 'pipe', env: { ...process.env, TASKMASTER_LOG_LEVEL: 'error' } }); const expectedPath = getExpectedHamsterPath(profile, tempDir); // Strictly enforce that all profiles with default rules must have hamster mapping expect(expectedPath).not.toBeNull(); // Verify hamster file does NOT exist in solo mode expect(fs.existsSync(expectedPath)).toBe(false); } finally { fs.rmSync(tempDir, { recursive: true, force: true }); } }); }); }); describe('Default mode behavior (no --mode flag)', () => { // When no mode is specified, default is 'solo' (no auth/config present) // This means hamster.mdc should NOT be distributed by default PROFILES_WITH_DEFAULT_RULES.forEach((profile) => { test(`${profile} profile defaults to solo mode (no hamster rules without --mode flag)`, () => { const tempDir = fs.mkdtempSync( path.join(os.tmpdir(), `tm-hamster-default-test-${profile}-`) ); try { // Run without mode flag - should default to solo execSync(`node ${CLI_PATH} rules add ${profile}`, { cwd: tempDir, stdio: 'pipe', env: { ...process.env, TASKMASTER_LOG_LEVEL: 'error' } }); const expectedPath = getExpectedHamsterPath(profile, tempDir); // Strictly enforce that all profiles with default rules must have hamster mapping expect(expectedPath).not.toBeNull(); // Default is solo mode, so hamster file should NOT exist expect(fs.existsSync(expectedPath)).toBe(false); } finally { fs.rmSync(tempDir, { recursive: true, force: true }); } }); }); }); describe('Profiles without default rules', () => { // These profiles use different mechanisms (CLAUDE.md, AGENTS.md, etc.) // and don't include the defaultFileMap rules PROFILES_WITHOUT_DEFAULT_RULES.forEach((profile) => { test(`${profile} profile does not use defaultFileMap (has custom mechanism)`, async () => { // Dynamically import the profile to check its configuration const profileModule = await import( `../../../src/profiles/${profile}.js` ); const profileExport = profileModule[`${profile}Profile`]; // These profiles should have includeDefaultRules: false // which means they won't automatically get hamster files via defaultFileMap expect(profileExport.includeDefaultRules).toBe(false); }); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/eyaltoledano/claude-task-master'

If you have feedback or need assistance with the MCP directory API, please join our Discord server