feat: add kiro profile (#1001)
* feat: add kiro profile * chore: fix format * chore: implement requested changes * chore: fix CI
This commit is contained in:
9
.changeset/add-kiro-profile.md
Normal file
9
.changeset/add-kiro-profile.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add Kiro editor rule profile support
|
||||||
|
|
||||||
|
- Add support for Kiro IDE with custom rule files and MCP configuration
|
||||||
|
- Generate rule files in `.kiro/steering/` directory with markdown format
|
||||||
|
- Include MCP server configuration with enhanced file inclusion patterns
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @typedef {'amp' | 'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'opencode' | 'roo' | 'trae' | 'windsurf' | 'vscode' | 'zed'} RulesProfile
|
* @typedef {'amp' | 'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'kiro' | 'opencode' | 'roo' | 'trae' | 'windsurf' | 'vscode' | 'zed'} RulesProfile
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
* - codex: Codex integration
|
* - codex: Codex integration
|
||||||
* - cursor: Cursor IDE rules
|
* - cursor: Cursor IDE rules
|
||||||
* - gemini: Gemini integration
|
* - gemini: Gemini integration
|
||||||
|
* - kiro: Kiro IDE rules
|
||||||
* - opencode: OpenCode integration
|
* - opencode: OpenCode integration
|
||||||
* - roo: Roo Code IDE rules
|
* - roo: Roo Code IDE rules
|
||||||
* - trae: Trae IDE rules
|
* - trae: Trae IDE rules
|
||||||
@@ -35,6 +36,7 @@ export const RULE_PROFILES = [
|
|||||||
'codex',
|
'codex',
|
||||||
'cursor',
|
'cursor',
|
||||||
'gemini',
|
'gemini',
|
||||||
|
'kiro',
|
||||||
'opencode',
|
'opencode',
|
||||||
'roo',
|
'roo',
|
||||||
'trae',
|
'trae',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export { clineProfile } from './cline.js';
|
|||||||
export { codexProfile } from './codex.js';
|
export { codexProfile } from './codex.js';
|
||||||
export { cursorProfile } from './cursor.js';
|
export { cursorProfile } from './cursor.js';
|
||||||
export { geminiProfile } from './gemini.js';
|
export { geminiProfile } from './gemini.js';
|
||||||
|
export { kiroProfile } from './kiro.js';
|
||||||
export { opencodeProfile } from './opencode.js';
|
export { opencodeProfile } from './opencode.js';
|
||||||
export { rooProfile } from './roo.js';
|
export { rooProfile } from './roo.js';
|
||||||
export { traeProfile } from './trae.js';
|
export { traeProfile } from './trae.js';
|
||||||
|
|||||||
42
src/profiles/kiro.js
Normal file
42
src/profiles/kiro.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Kiro profile for rule-transformer
|
||||||
|
import { createProfile } from './base-profile.js';
|
||||||
|
|
||||||
|
// Create and export kiro profile using the base factory
|
||||||
|
export const kiroProfile = createProfile({
|
||||||
|
name: 'kiro',
|
||||||
|
displayName: 'Kiro',
|
||||||
|
url: 'kiro.dev',
|
||||||
|
docsUrl: 'kiro.dev/docs',
|
||||||
|
profileDir: '.kiro',
|
||||||
|
rulesDir: '.kiro/steering', // Kiro rules location (full path)
|
||||||
|
mcpConfig: true,
|
||||||
|
mcpConfigName: 'settings/mcp.json', // Create directly in settings subdirectory
|
||||||
|
includeDefaultRules: true, // Include default rules to get all the standard files
|
||||||
|
targetExtension: '.md',
|
||||||
|
fileMap: {
|
||||||
|
// Override specific mappings - the base profile will create:
|
||||||
|
// 'rules/cursor_rules.mdc': 'kiro_rules.md'
|
||||||
|
// 'rules/dev_workflow.mdc': 'dev_workflow.md'
|
||||||
|
// 'rules/self_improve.mdc': 'self_improve.md'
|
||||||
|
// 'rules/taskmaster.mdc': 'taskmaster.md'
|
||||||
|
// We can add additional custom mappings here if needed
|
||||||
|
},
|
||||||
|
customReplacements: [
|
||||||
|
// Core Kiro directory structure changes
|
||||||
|
{ from: /\.cursor\/rules/g, to: '.kiro/steering' },
|
||||||
|
{ from: /\.cursor\/mcp\.json/g, to: '.kiro/settings/mcp.json' },
|
||||||
|
|
||||||
|
// Fix any remaining kiro/rules references that might be created during transformation
|
||||||
|
{ from: /\.kiro\/rules/g, to: '.kiro/steering' },
|
||||||
|
|
||||||
|
// Essential markdown link transformations for Kiro structure
|
||||||
|
{
|
||||||
|
from: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g,
|
||||||
|
to: '[$1](.kiro/steering/$2.md)'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Kiro specific terminology
|
||||||
|
{ from: /rules directory/g, to: 'steering directory' },
|
||||||
|
{ from: /cursor rules/gi, to: 'Kiro steering files' }
|
||||||
|
]
|
||||||
|
});
|
||||||
142
tests/unit/profiles/kiro-integration.test.js
Normal file
142
tests/unit/profiles/kiro-integration.test.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import os from 'os';
|
||||||
|
|
||||||
|
// Mock external modules
|
||||||
|
jest.mock('child_process', () => ({
|
||||||
|
execSync: jest.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock console methods
|
||||||
|
jest.mock('console', () => ({
|
||||||
|
log: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
clear: jest.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Kiro Integration', () => {
|
||||||
|
let tempDir;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
// Create a temporary directory for testing
|
||||||
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
|
||||||
|
|
||||||
|
// Spy on fs methods
|
||||||
|
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
|
||||||
|
jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
|
||||||
|
if (filePath.toString().includes('mcp.json')) {
|
||||||
|
return JSON.stringify({ mcpServers: {} }, null, 2);
|
||||||
|
}
|
||||||
|
return '{}';
|
||||||
|
});
|
||||||
|
jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
|
||||||
|
jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Clean up the temporary directory
|
||||||
|
try {
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error cleaning up: ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test function that simulates the createProjectStructure behavior for Kiro files
|
||||||
|
function mockCreateKiroStructure() {
|
||||||
|
// This function simulates the actual kiro profile creation logic
|
||||||
|
// It explicitly calls the mocked fs methods to ensure consistency with the test environment
|
||||||
|
|
||||||
|
// Simulate directory creation calls - these will call the mocked mkdirSync
|
||||||
|
fs.mkdirSync(path.join(tempDir, '.kiro'), { recursive: true });
|
||||||
|
fs.mkdirSync(path.join(tempDir, '.kiro', 'steering'), { recursive: true });
|
||||||
|
fs.mkdirSync(path.join(tempDir, '.kiro', 'settings'), { recursive: true });
|
||||||
|
|
||||||
|
// Create MCP config file at .kiro/settings/mcp.json
|
||||||
|
// This will call the mocked writeFileSync
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(tempDir, '.kiro', 'settings', 'mcp.json'),
|
||||||
|
JSON.stringify({ mcpServers: {} }, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create kiro rule files in steering directory
|
||||||
|
// All these will call the mocked writeFileSync
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(tempDir, '.kiro', 'steering', 'kiro_rules.md'),
|
||||||
|
'# Kiro Rules\n\nKiro-specific rules and instructions.'
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(tempDir, '.kiro', 'steering', 'dev_workflow.md'),
|
||||||
|
'# Development Workflow\n\nDevelopment workflow instructions.'
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(tempDir, '.kiro', 'steering', 'self_improve.md'),
|
||||||
|
'# Self Improvement\n\nSelf improvement guidelines.'
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(tempDir, '.kiro', 'steering', 'taskmaster.md'),
|
||||||
|
'# Task Master\n\nTask Master integration instructions.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('creates all required .kiro directories', () => {
|
||||||
|
// Act
|
||||||
|
mockCreateKiroStructure();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.kiro'), {
|
||||||
|
recursive: true
|
||||||
|
});
|
||||||
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||||
|
path.join(tempDir, '.kiro', 'steering'),
|
||||||
|
{
|
||||||
|
recursive: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||||
|
path.join(tempDir, '.kiro', 'settings'),
|
||||||
|
{
|
||||||
|
recursive: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creates Kiro mcp.json with mcpServers format', () => {
|
||||||
|
// Act
|
||||||
|
mockCreateKiroStructure();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||||
|
path.join(tempDir, '.kiro', 'settings', 'mcp.json'),
|
||||||
|
JSON.stringify({ mcpServers: {} }, null, 2)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creates rule files in steering directory', () => {
|
||||||
|
// Act
|
||||||
|
mockCreateKiroStructure();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||||
|
path.join(tempDir, '.kiro', 'steering', 'kiro_rules.md'),
|
||||||
|
'# Kiro Rules\n\nKiro-specific rules and instructions.'
|
||||||
|
);
|
||||||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||||
|
path.join(tempDir, '.kiro', 'steering', 'dev_workflow.md'),
|
||||||
|
'# Development Workflow\n\nDevelopment workflow instructions.'
|
||||||
|
);
|
||||||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||||
|
path.join(tempDir, '.kiro', 'steering', 'self_improve.md'),
|
||||||
|
'# Self Improvement\n\nSelf improvement guidelines.'
|
||||||
|
);
|
||||||
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||||
|
path.join(tempDir, '.kiro', 'steering', 'taskmaster.md'),
|
||||||
|
'# Task Master\n\nTask Master integration instructions.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -41,6 +41,12 @@ describe('MCP Configuration Validation', () => {
|
|||||||
expectedConfigName: 'settings.json',
|
expectedConfigName: 'settings.json',
|
||||||
expectedPath: '.gemini/settings.json'
|
expectedPath: '.gemini/settings.json'
|
||||||
},
|
},
|
||||||
|
kiro: {
|
||||||
|
shouldHaveMcp: true,
|
||||||
|
expectedDir: '.kiro',
|
||||||
|
expectedConfigName: 'settings/mcp.json',
|
||||||
|
expectedPath: '.kiro/settings/mcp.json'
|
||||||
|
},
|
||||||
opencode: {
|
opencode: {
|
||||||
shouldHaveMcp: true,
|
shouldHaveMcp: true,
|
||||||
expectedDir: '.',
|
expectedDir: '.',
|
||||||
@@ -128,6 +134,7 @@ describe('MCP Configuration Validation', () => {
|
|||||||
|
|
||||||
test('should ensure all MCP-enabled profiles use proper directory structure', () => {
|
test('should ensure all MCP-enabled profiles use proper directory structure', () => {
|
||||||
const rootProfiles = ['opencode', 'claude', 'codex']; // Profiles that use root directory for config
|
const rootProfiles = ['opencode', 'claude', 'codex']; // Profiles that use root directory for config
|
||||||
|
const nestedConfigProfiles = ['kiro']; // Profiles that use nested directories for config
|
||||||
|
|
||||||
RULE_PROFILES.forEach((profileName) => {
|
RULE_PROFILES.forEach((profileName) => {
|
||||||
const profile = getRulesProfile(profileName);
|
const profile = getRulesProfile(profileName);
|
||||||
@@ -140,6 +147,11 @@ describe('MCP Configuration Validation', () => {
|
|||||||
// Other root profiles normalize to just the filename (no ./ prefix)
|
// Other root profiles normalize to just the filename (no ./ prefix)
|
||||||
expect(profile.mcpConfigPath).toMatch(/^[\w_.]+$/);
|
expect(profile.mcpConfigPath).toMatch(/^[\w_.]+$/);
|
||||||
}
|
}
|
||||||
|
} else if (nestedConfigProfiles.includes(profileName)) {
|
||||||
|
// Profiles with nested config directories
|
||||||
|
expect(profile.mcpConfigPath).toMatch(
|
||||||
|
/^\.[\w-]+\/[\w-]+\/[\w_.]+$/
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Other profiles should have config files in their specific directories
|
// Other profiles should have config files in their specific directories
|
||||||
expect(profile.mcpConfigPath).toMatch(/^\.[\w-]+\/[\w_.]+$/);
|
expect(profile.mcpConfigPath).toMatch(/^\.[\w-]+\/[\w_.]+$/);
|
||||||
@@ -347,6 +359,13 @@ describe('MCP Configuration Validation', () => {
|
|||||||
// Other root profiles normalize to just the filename
|
// Other root profiles normalize to just the filename
|
||||||
expect(profile.mcpConfigPath).toBe(profile.mcpConfigName);
|
expect(profile.mcpConfigPath).toBe(profile.mcpConfigName);
|
||||||
}
|
}
|
||||||
|
} else if (profileName === 'kiro') {
|
||||||
|
// Kiro has a nested config structure
|
||||||
|
const parts = profile.mcpConfigPath.split('/');
|
||||||
|
expect(parts).toHaveLength(3); // Should be profileDir/settings/mcp.json
|
||||||
|
expect(parts[0]).toBe(profile.profileDir);
|
||||||
|
expect(parts[1]).toBe('settings');
|
||||||
|
expect(parts[2]).toBe('mcp.json');
|
||||||
} else {
|
} else {
|
||||||
// Non-root profiles should have profileDir/configName structure
|
// Non-root profiles should have profileDir/configName structure
|
||||||
const parts = profile.mcpConfigPath.split('/');
|
const parts = profile.mcpConfigPath.split('/');
|
||||||
|
|||||||
215
tests/unit/profiles/rule-transformer-kiro.test.js
Normal file
215
tests/unit/profiles/rule-transformer-kiro.test.js
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
|
// Mock fs module before importing anything that uses it
|
||||||
|
jest.mock('fs', () => ({
|
||||||
|
readFileSync: jest.fn(),
|
||||||
|
writeFileSync: jest.fn(),
|
||||||
|
existsSync: jest.fn(),
|
||||||
|
mkdirSync: jest.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Import modules after mocking
|
||||||
|
import fs from 'fs';
|
||||||
|
import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
|
||||||
|
import { kiroProfile } from '../../../src/profiles/kiro.js';
|
||||||
|
|
||||||
|
describe('Kiro Rule Transformer', () => {
|
||||||
|
// Set up spies on the mocked modules
|
||||||
|
const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
|
||||||
|
const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
|
||||||
|
const mockExistsSync = jest.spyOn(fs, 'existsSync');
|
||||||
|
const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
|
||||||
|
const mockConsoleError = jest
|
||||||
|
.spyOn(console, 'error')
|
||||||
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
// Setup default mocks
|
||||||
|
mockReadFileSync.mockReturnValue('');
|
||||||
|
mockWriteFileSync.mockImplementation(() => {});
|
||||||
|
mockExistsSync.mockReturnValue(true);
|
||||||
|
mockMkdirSync.mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly convert basic terms', () => {
|
||||||
|
const testContent = `---
|
||||||
|
description: Test Cursor rule for basic terms
|
||||||
|
globs: **/*
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
|
||||||
|
Also has references to .mdc files.`;
|
||||||
|
|
||||||
|
// Mock file read to return our test content
|
||||||
|
mockReadFileSync.mockReturnValue(testContent);
|
||||||
|
|
||||||
|
// Mock file system operations
|
||||||
|
mockExistsSync.mockReturnValue(true);
|
||||||
|
|
||||||
|
// Call the function
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
kiroProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify the result
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Get the transformed content
|
||||||
|
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||||
|
|
||||||
|
// Verify Cursor -> Kiro transformations
|
||||||
|
expect(transformedContent).toContain('kiro.dev');
|
||||||
|
expect(transformedContent).toContain('Kiro');
|
||||||
|
expect(transformedContent).not.toContain('cursor.so');
|
||||||
|
expect(transformedContent).not.toContain('Cursor');
|
||||||
|
expect(transformedContent).toContain('.md');
|
||||||
|
expect(transformedContent).not.toContain('.mdc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle URL transformations', () => {
|
||||||
|
const testContent = `Visit https://cursor.so/docs for more information.
|
||||||
|
Also check out cursor.so and www.cursor.so for updates.`;
|
||||||
|
|
||||||
|
mockReadFileSync.mockReturnValue(testContent);
|
||||||
|
mockExistsSync.mockReturnValue(true);
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
kiroProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||||
|
|
||||||
|
// Verify URL transformations
|
||||||
|
expect(transformedContent).toContain('https://kiro.dev');
|
||||||
|
expect(transformedContent).toContain('kiro.dev');
|
||||||
|
expect(transformedContent).not.toContain('cursor.so');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle file extension transformations', () => {
|
||||||
|
const testContent = `This rule references file.mdc and another.mdc file.
|
||||||
|
Use the .mdc extension for all rule files.`;
|
||||||
|
|
||||||
|
mockReadFileSync.mockReturnValue(testContent);
|
||||||
|
mockExistsSync.mockReturnValue(true);
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
kiroProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||||
|
|
||||||
|
// Verify file extension transformations
|
||||||
|
expect(transformedContent).toContain('file.md');
|
||||||
|
expect(transformedContent).toContain('another.md');
|
||||||
|
expect(transformedContent).toContain('.md extension');
|
||||||
|
expect(transformedContent).not.toContain('.mdc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle case variations', () => {
|
||||||
|
const testContent = `CURSOR, Cursor, cursor should all be transformed.`;
|
||||||
|
|
||||||
|
mockReadFileSync.mockReturnValue(testContent);
|
||||||
|
mockExistsSync.mockReturnValue(true);
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
kiroProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||||
|
|
||||||
|
// Verify case transformations
|
||||||
|
// Due to regex order, the case-insensitive rule runs first:
|
||||||
|
// CURSOR -> Kiro (because it starts with 'C'), Cursor -> Kiro, cursor -> kiro
|
||||||
|
expect(transformedContent).toContain('Kiro');
|
||||||
|
expect(transformedContent).toContain('kiro');
|
||||||
|
expect(transformedContent).not.toContain('CURSOR');
|
||||||
|
expect(transformedContent).not.toContain('Cursor');
|
||||||
|
expect(transformedContent).not.toContain('cursor');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create target directory if it does not exist', () => {
|
||||||
|
const testContent = 'Test content';
|
||||||
|
mockReadFileSync.mockReturnValue(testContent);
|
||||||
|
mockExistsSync.mockReturnValue(false);
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'nested/path/test-target.md',
|
||||||
|
kiroProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(mockMkdirSync).toHaveBeenCalledWith('nested/path', {
|
||||||
|
recursive: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle file system errors gracefully', () => {
|
||||||
|
mockReadFileSync.mockImplementation(() => {
|
||||||
|
throw new Error('File not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
kiroProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
expect(mockConsoleError).toHaveBeenCalledWith(
|
||||||
|
'Error converting rule file: File not found'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle write errors gracefully', () => {
|
||||||
|
mockReadFileSync.mockReturnValue('Test content');
|
||||||
|
mockWriteFileSync.mockImplementation(() => {
|
||||||
|
throw new Error('Write permission denied');
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = convertRuleToProfileRule(
|
||||||
|
'test-source.mdc',
|
||||||
|
'test-target.md',
|
||||||
|
kiroProfile
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
expect(mockConsoleError).toHaveBeenCalledWith(
|
||||||
|
'Error converting rule file: Write permission denied'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should verify profile configuration', () => {
|
||||||
|
expect(kiroProfile.profileName).toBe('kiro');
|
||||||
|
expect(kiroProfile.displayName).toBe('Kiro');
|
||||||
|
expect(kiroProfile.profileDir).toBe('.kiro');
|
||||||
|
expect(kiroProfile.mcpConfig).toBe(true);
|
||||||
|
expect(kiroProfile.mcpConfigName).toBe('settings/mcp.json');
|
||||||
|
expect(kiroProfile.mcpConfigPath).toBe('.kiro/settings/mcp.json');
|
||||||
|
expect(kiroProfile.includeDefaultRules).toBe(true);
|
||||||
|
expect(kiroProfile.fileMap).toEqual({
|
||||||
|
'rules/cursor_rules.mdc': 'kiro_rules.md',
|
||||||
|
'rules/dev_workflow.mdc': 'dev_workflow.md',
|
||||||
|
'rules/self_improve.mdc': 'self_improve.md',
|
||||||
|
'rules/taskmaster.mdc': 'taskmaster.md'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -19,6 +19,7 @@ describe('Rule Transformer - General', () => {
|
|||||||
'codex',
|
'codex',
|
||||||
'cursor',
|
'cursor',
|
||||||
'gemini',
|
'gemini',
|
||||||
|
'kiro',
|
||||||
'opencode',
|
'opencode',
|
||||||
'roo',
|
'roo',
|
||||||
'trae',
|
'trae',
|
||||||
@@ -212,6 +213,11 @@ describe('Rule Transformer - General', () => {
|
|||||||
mcpConfigName: 'settings.json',
|
mcpConfigName: 'settings.json',
|
||||||
expectedPath: '.gemini/settings.json'
|
expectedPath: '.gemini/settings.json'
|
||||||
},
|
},
|
||||||
|
kiro: {
|
||||||
|
mcpConfig: true,
|
||||||
|
mcpConfigName: 'settings/mcp.json',
|
||||||
|
expectedPath: '.kiro/settings/mcp.json'
|
||||||
|
},
|
||||||
opencode: {
|
opencode: {
|
||||||
mcpConfig: true,
|
mcpConfig: true,
|
||||||
mcpConfigName: 'opencode.json',
|
mcpConfigName: 'opencode.json',
|
||||||
|
|||||||
Reference in New Issue
Block a user