* centralize prompt management * add changeset * add variant key to determine prompt version * update tests and add prompt manager test * determine internal path, don't use projectRoot * add promptManager mock * detailed prompt docs * add schemas and validator packages * add validate prompts command * add schema validation * update tests * move schemas to src/prompts/schemas * use this.promptsDir for better semantics * add prompt schemas * version schema files & update links * remove validate command * expect dependencies * update docs * fix test * remove suggestmode to ensure clean keys * remove default variant from research and update schema * now handled by prompt manager * add manual test to verify prompts * remove incorrect batch variant * consolidate variants * consolidate analyze-complexity to just default variant * consolidate parse-prd variants * add eq handler for handlebars * consolidate research prompt variants * use brevity * consolidate variants for update subtask * add not handler * consolidate variants for update-task * consolidate update-tasks variants * add conditional content to prompt when research used * update prompt tests * show correct research variant * make variant names link to below * remove changset * restore gitignore * Merge branch 'next' of https://github.com/eyaltoledano/claude-task-master into joedanz/centralize-prompts # Conflicts: # package-lock.json # scripts/modules/task-manager/expand-task.js # scripts/modules/task-manager/parse-prd.js remove unused * add else * update tests * update biome optional dependencies * responsive html output for mobile
344 lines
9.0 KiB
JavaScript
344 lines
9.0 KiB
JavaScript
import { jest } from '@jest/globals';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import os from 'os';
|
|
// Mock the schema integration functions to avoid chalk issues
|
|
const mockSetupSchemaIntegration = jest.fn();
|
|
|
|
import { vscodeProfile } from '../../../src/profiles/vscode.js';
|
|
|
|
// Mock external modules
|
|
jest.mock('child_process', () => ({
|
|
execSync: jest.fn()
|
|
}));
|
|
|
|
// Mock fs/promises
|
|
const mockFsPromises = {
|
|
mkdir: jest.fn(),
|
|
access: jest.fn(),
|
|
copyFile: jest.fn(),
|
|
readFile: jest.fn(),
|
|
writeFile: jest.fn()
|
|
};
|
|
|
|
jest.mock('fs/promises', () => mockFsPromises);
|
|
|
|
// Mock console methods
|
|
jest.mock('console', () => ({
|
|
log: jest.fn(),
|
|
info: jest.fn(),
|
|
warn: jest.fn(),
|
|
error: jest.fn(),
|
|
clear: jest.fn()
|
|
}));
|
|
|
|
describe('VS Code 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: {
|
|
'task-master-ai': {
|
|
command: 'node',
|
|
args: ['mcp-server/src/index.js']
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (filePath.toString().includes('instructions')) {
|
|
return 'VS Code instruction content';
|
|
}
|
|
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 VS Code files
|
|
function mockCreateVSCodeStructure() {
|
|
// Create .vscode directory for MCP configuration
|
|
fs.mkdirSync(path.join(tempDir, '.vscode'), { recursive: true });
|
|
|
|
// Create .github/instructions directory for VS Code custom instructions
|
|
fs.mkdirSync(path.join(tempDir, '.github', 'instructions'), {
|
|
recursive: true
|
|
});
|
|
fs.mkdirSync(path.join(tempDir, '.github', 'instructions', 'taskmaster'), {
|
|
recursive: true
|
|
});
|
|
|
|
// Create MCP configuration file
|
|
const mcpConfig = {
|
|
mcpServers: {
|
|
'task-master-ai': {
|
|
command: 'node',
|
|
args: ['mcp-server/src/index.js'],
|
|
env: {
|
|
PROJECT_ROOT: process.cwd()
|
|
}
|
|
}
|
|
}
|
|
};
|
|
fs.writeFileSync(
|
|
path.join(tempDir, '.vscode', 'mcp.json'),
|
|
JSON.stringify(mcpConfig, null, 2)
|
|
);
|
|
|
|
// Create sample instruction files
|
|
const instructionFiles = [
|
|
'vscode_rules.md',
|
|
'dev_workflow.md',
|
|
'self_improve.md'
|
|
];
|
|
|
|
for (const file of instructionFiles) {
|
|
const content = `---
|
|
description: VS Code instruction for ${file}
|
|
applyTo: "**/*.ts,**/*.tsx,**/*.js,**/*.jsx"
|
|
alwaysApply: true
|
|
---
|
|
|
|
# ${file.replace('.md', '').replace('_', ' ').toUpperCase()}
|
|
|
|
This is a VS Code custom instruction file.`;
|
|
|
|
fs.writeFileSync(
|
|
path.join(tempDir, '.github', 'instructions', file),
|
|
content
|
|
);
|
|
}
|
|
|
|
// Create taskmaster subdirectory with additional instructions
|
|
const taskmasterFiles = ['taskmaster.md', 'commands.md', 'architecture.md'];
|
|
|
|
for (const file of taskmasterFiles) {
|
|
const content = `---
|
|
description: Task Master specific instruction for ${file}
|
|
applyTo: "**/*.ts,**/*.js"
|
|
alwaysApply: true
|
|
---
|
|
|
|
# ${file.replace('.md', '').toUpperCase()}
|
|
|
|
Task Master specific VS Code instruction.`;
|
|
|
|
fs.writeFileSync(
|
|
path.join(tempDir, '.github', 'instructions', 'taskmaster', file),
|
|
content
|
|
);
|
|
}
|
|
}
|
|
|
|
test('creates all required VS Code directories', () => {
|
|
// Act
|
|
mockCreateVSCodeStructure();
|
|
|
|
// Assert - .vscode directory for MCP config
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.vscode'), {
|
|
recursive: true
|
|
});
|
|
|
|
// Assert - .github/instructions directory for custom instructions
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.github', 'instructions'),
|
|
{ recursive: true }
|
|
);
|
|
|
|
// Assert - taskmaster subdirectory
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
path.join(tempDir, '.github', 'instructions', 'taskmaster'),
|
|
{ recursive: true }
|
|
);
|
|
});
|
|
|
|
test('creates VS Code MCP configuration file', () => {
|
|
// Act
|
|
mockCreateVSCodeStructure();
|
|
|
|
// Assert
|
|
const expectedMcpPath = path.join(tempDir, '.vscode', 'mcp.json');
|
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
expectedMcpPath,
|
|
expect.stringContaining('task-master-ai')
|
|
);
|
|
});
|
|
|
|
test('creates VS Code instruction files with applyTo patterns', () => {
|
|
// Act
|
|
mockCreateVSCodeStructure();
|
|
|
|
// Assert main instruction files
|
|
const mainInstructionFiles = [
|
|
'vscode_rules.md',
|
|
'dev_workflow.md',
|
|
'self_improve.md'
|
|
];
|
|
|
|
for (const file of mainInstructionFiles) {
|
|
const expectedPath = path.join(tempDir, '.github', 'instructions', file);
|
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
expectedPath,
|
|
expect.stringContaining('applyTo:')
|
|
);
|
|
}
|
|
});
|
|
|
|
test('creates taskmaster specific instruction files', () => {
|
|
// Act
|
|
mockCreateVSCodeStructure();
|
|
|
|
// Assert taskmaster subdirectory files
|
|
const taskmasterFiles = ['taskmaster.md', 'commands.md', 'architecture.md'];
|
|
|
|
for (const file of taskmasterFiles) {
|
|
const expectedPath = path.join(
|
|
tempDir,
|
|
'.github',
|
|
'instructions',
|
|
'taskmaster',
|
|
file
|
|
);
|
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
expectedPath,
|
|
expect.stringContaining('applyTo:')
|
|
);
|
|
}
|
|
});
|
|
|
|
test('VS Code instruction files use applyTo instead of globs', () => {
|
|
// Act
|
|
mockCreateVSCodeStructure();
|
|
|
|
// Get all the writeFileSync calls for .md files
|
|
const mdFileWrites = fs.writeFileSync.mock.calls.filter((call) =>
|
|
call[0].toString().endsWith('.md')
|
|
);
|
|
|
|
// Assert that all .md files contain applyTo and not globs
|
|
for (const writeCall of mdFileWrites) {
|
|
const content = writeCall[1];
|
|
expect(content).toContain('applyTo:');
|
|
expect(content).not.toContain('globs:');
|
|
}
|
|
});
|
|
|
|
test('MCP configuration includes correct structure for VS Code', () => {
|
|
// Act
|
|
mockCreateVSCodeStructure();
|
|
|
|
// Get the MCP config write call
|
|
const mcpConfigWrite = fs.writeFileSync.mock.calls.find((call) =>
|
|
call[0].toString().includes('mcp.json')
|
|
);
|
|
|
|
expect(mcpConfigWrite).toBeDefined();
|
|
|
|
const mcpContent = mcpConfigWrite[1];
|
|
const mcpConfig = JSON.parse(mcpContent);
|
|
|
|
// Assert MCP structure
|
|
expect(mcpConfig).toHaveProperty('mcpServers');
|
|
expect(mcpConfig.mcpServers).toHaveProperty('task-master-ai');
|
|
expect(mcpConfig.mcpServers['task-master-ai']).toHaveProperty(
|
|
'command',
|
|
'node'
|
|
);
|
|
expect(mcpConfig.mcpServers['task-master-ai']).toHaveProperty('args');
|
|
expect(mcpConfig.mcpServers['task-master-ai'].args).toContain(
|
|
'mcp-server/src/index.js'
|
|
);
|
|
});
|
|
|
|
test('directory structure follows VS Code conventions', () => {
|
|
// Act
|
|
mockCreateVSCodeStructure();
|
|
|
|
// Assert the specific directory structure VS Code expects
|
|
const expectedDirs = [
|
|
path.join(tempDir, '.vscode'),
|
|
path.join(tempDir, '.github', 'instructions'),
|
|
path.join(tempDir, '.github', 'instructions', 'taskmaster')
|
|
];
|
|
|
|
for (const dir of expectedDirs) {
|
|
expect(fs.mkdirSync).toHaveBeenCalledWith(dir, { recursive: true });
|
|
}
|
|
});
|
|
|
|
test('instruction files contain VS Code specific formatting', () => {
|
|
// Act
|
|
mockCreateVSCodeStructure();
|
|
|
|
// Get a sample instruction file write
|
|
const instructionWrite = fs.writeFileSync.mock.calls.find((call) =>
|
|
call[0].toString().includes('vscode_rules.md')
|
|
);
|
|
|
|
expect(instructionWrite).toBeDefined();
|
|
|
|
const content = instructionWrite[1];
|
|
|
|
// Assert VS Code specific patterns
|
|
expect(content).toContain('---'); // YAML frontmatter
|
|
expect(content).toContain('description:');
|
|
expect(content).toContain('applyTo:');
|
|
expect(content).toContain('alwaysApply:');
|
|
expect(content).toContain('**/*.ts'); // File patterns in quotes
|
|
});
|
|
|
|
describe('Schema Integration', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
// Replace the onAddRulesProfile function with our mock
|
|
vscodeProfile.onAddRulesProfile = mockSetupSchemaIntegration;
|
|
});
|
|
|
|
test('setupSchemaIntegration is called with project root', async () => {
|
|
// Arrange
|
|
mockSetupSchemaIntegration.mockResolvedValue();
|
|
|
|
// Act
|
|
await vscodeProfile.onAddRulesProfile(tempDir);
|
|
|
|
// Assert
|
|
expect(mockSetupSchemaIntegration).toHaveBeenCalledWith(tempDir);
|
|
});
|
|
|
|
test('schema integration function exists and is callable', () => {
|
|
// Assert that the VS Code profile has the schema integration function
|
|
expect(vscodeProfile.onAddRulesProfile).toBeDefined();
|
|
expect(typeof vscodeProfile.onAddRulesProfile).toBe('function');
|
|
});
|
|
|
|
test('schema integration handles errors gracefully', async () => {
|
|
// Arrange
|
|
mockSetupSchemaIntegration.mockRejectedValue(
|
|
new Error('Schema setup failed')
|
|
);
|
|
|
|
// Act & Assert - Should propagate the error
|
|
await expect(vscodeProfile.onAddRulesProfile(tempDir)).rejects.toThrow(
|
|
'Schema setup failed'
|
|
);
|
|
});
|
|
});
|
|
});
|