fix formatting

This commit is contained in:
Joe Danziger
2025-05-09 12:21:51 -04:00
parent 476048b184
commit d48a3d3edc
7 changed files with 129 additions and 91 deletions

View File

@@ -218,6 +218,7 @@ task-master init --rules cursor,windsurf
- You can use multiple comma-separated brands in a single command. - You can use multiple comma-separated brands in a single command.
**Example:** **Example:**
```bash ```bash
task-master init --rules cursor,roo task-master init --rules cursor,roo
``` ```
@@ -238,6 +239,7 @@ task-master rules remove <brand1,brand2,...>
- You can use multiple comma-separated brands in a single command. - You can use multiple comma-separated brands in a single command.
**Examples:** **Examples:**
```bash ```bash
task-master rules add windsurf,roo task-master rules add windsurf,roo
task-master rules remove windsurf task-master rules remove windsurf

View File

@@ -80,9 +80,9 @@ export async function initializeProjectDirect(args, log, context = {}) {
// Handle rules option just like CLI // Handle rules option just like CLI
if (Array.isArray(args.rules) && args.rules.length > 0) { if (Array.isArray(args.rules) && args.rules.length > 0) {
options.rules = args.rules; options.rules = args.rules;
log.info(`Including brand rules: ${args.rules.join(", ")}`); log.info(`Including brand rules: ${args.rules.join(', ')}`);
} else { } else {
options.rules = ["cursor"]; options.rules = ['cursor'];
log.info(`No rules specified, defaulting to: cursor`); log.info(`No rules specified, defaulting to: cursor`);
} }

View File

@@ -24,7 +24,12 @@ export async function rulesDirect(args, log, context = {}) {
enableSilentMode(); enableSilentMode();
try { try {
const { action, rules, projectRoot, yes } = args; const { action, rules, projectRoot, yes } = args;
if (!action || !Array.isArray(rules) || rules.length === 0 || !projectRoot) { if (
!action ||
!Array.isArray(rules) ||
rules.length === 0 ||
!projectRoot
) {
return { return {
success: false, success: false,
error: { error: {
@@ -35,7 +40,8 @@ export async function rulesDirect(args, log, context = {}) {
} }
const rulesList = rules.join(','); const rulesList = rules.join(',');
const yesFlag = yes !== false ? '--yes' : ''; const yesFlag = yes !== false ? '--yes' : '';
const cmd = `npx task-master rules ${action} ${rulesList} ${yesFlag}`.trim(); const cmd =
`npx task-master rules ${action} ${rulesList} ${yesFlag}`.trim();
log.info(`[rulesDirect] Running: ${cmd} in ${projectRoot}`); log.info(`[rulesDirect] Running: ${cmd} in ${projectRoot}`);
const output = execSync(cmd, { cwd: projectRoot, encoding: 'utf8' }); const output = execSync(cmd, { cwd: projectRoot, encoding: 'utf8' });
log.info(`[rulesDirect] Output: ${output}`); log.info(`[rulesDirect] Output: ${output}`);

View File

@@ -39,7 +39,9 @@ export function registerInitializeProjectTool(server) {
rules: z rules: z
.array(z.string()) .array(z.string())
.optional() .optional()
.describe('List of brand rules to include at initialization (e.g., ["cursor", "roo"]). If omitted, defaults to ["cursor"].') .describe(
'List of brand rules to include at initialization (e.g., ["cursor", "roo"]). If omitted, defaults to ["cursor"].'
)
}), }),
execute: withNormalizedProjectRoot(async (args, context) => { execute: withNormalizedProjectRoot(async (args, context) => {
const { log } = context; const { log } = context;

View File

@@ -5,9 +5,9 @@
import { z } from 'zod'; import { z } from 'zod';
import { import {
createErrorResponse, createErrorResponse,
handleApiResult, handleApiResult,
withNormalizedProjectRoot withNormalizedProjectRoot
} from './utils.js'; } from './utils.js';
import { rulesDirect } from '../core/direct-functions/rules.js'; import { rulesDirect } from '../core/direct-functions/rules.js';
@@ -16,24 +16,42 @@ import { rulesDirect } from '../core/direct-functions/rules.js';
* @param {Object} server - FastMCP server instance * @param {Object} server - FastMCP server instance
*/ */
export function registerRulesTool(server) { export function registerRulesTool(server) {
server.addTool({ server.addTool({
name: 'rules', name: 'rules',
description: 'Add or remove brand rules and MCP config from the project (mirrors CLI rules add/remove).', description:
parameters: z.object({ 'Add or remove brand rules and MCP config from the project (mirrors CLI rules add/remove).',
action: z.enum(['add', 'remove']).describe('Whether to add or remove brand rules.'), parameters: z.object({
rules: z.array(z.string()).min(1).describe('List of brand rules to add or remove (e.g., ["roo", "windsurf"]).'), action: z
projectRoot: z.string().describe('The root directory of the project. Must be an absolute path.'), .enum(['add', 'remove'])
yes: z.boolean().optional().default(true).describe('Run non-interactively (default: true).') .describe('Whether to add or remove brand rules.'),
}), rules: z
execute: withNormalizedProjectRoot(async (args, { log, session }) => { .array(z.string())
try { .min(1)
log.info(`[rules tool] Executing action: ${args.action} for rules: ${args.rules.join(', ')} in ${args.projectRoot}`); .describe(
const result = await rulesDirect(args, log, { session }); 'List of brand rules to add or remove (e.g., ["roo", "windsurf"]).'
return handleApiResult(result, log); ),
} catch (error) { projectRoot: z
log.error(`[rules tool] Error: ${error.message}`); .string()
return createErrorResponse(error.message, { details: error.stack }); .describe(
} 'The root directory of the project. Must be an absolute path.'
}) ),
}); yes: z
.boolean()
.optional()
.default(true)
.describe('Run non-interactively (default: true).')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
log.info(
`[rules tool] Executing action: ${args.action} for rules: ${args.rules.join(', ')} in ${args.projectRoot}`
);
const result = await rulesDirect(args, log, { session });
return handleApiResult(result, log);
} catch (error) {
log.error(`[rules tool] Error: ${error.message}`);
return createErrorResponse(error.message, { details: error.stack });
}
})
});
} }

View File

@@ -319,9 +319,10 @@ async function initializeProject(options = {}) {
// } // }
const skipPrompts = options.yes || (options.name && options.description); const skipPrompts = options.yes || (options.name && options.description);
let selectedBrandRules = options.rules && Array.isArray(options.rules) && options.rules.length > 0 let selectedBrandRules =
? options.rules options.rules && Array.isArray(options.rules) && options.rules.length > 0
: ['cursor']; ? options.rules
: ['cursor'];
// if (!isSilentMode()) { // if (!isSilentMode()) {
// console.log('Skip prompts determined:', skipPrompts); // console.log('Skip prompts determined:', skipPrompts);
@@ -383,8 +384,8 @@ let selectedBrandRules = options.rules && Array.isArray(options.rules) && option
console.log('\nTask Master Project settings:'); console.log('\nTask Master Project settings:');
console.log( console.log(
chalk.blue( chalk.blue(
'Add shell aliases (so you can use "tm" instead of "task-master"):') 'Add shell aliases (so you can use "tm" instead of "task-master"):'
, ),
chalk.white(addAliasesPrompted ? 'Yes' : 'No') chalk.white(addAliasesPrompted ? 'Yes' : 'No')
); );
@@ -401,10 +402,11 @@ let selectedBrandRules = options.rules && Array.isArray(options.rules) && option
return; return;
} }
// === Brand Rules Selection (Inquirer) === // === Brand Rules Selection (Inquirer) ===
console.log( console.log(
chalk.cyan('\nRules help enforce best practices and conventions for Task Master.') chalk.cyan(
'\nRules help enforce best practices and conventions for Task Master.'
)
); );
const brandRulesQuestion = { const brandRulesQuestion = {
type: 'checkbox', type: 'checkbox',
@@ -417,7 +419,6 @@ let selectedBrandRules = options.rules && Array.isArray(options.rules) && option
const { brandRules } = await inquirer.prompt([brandRulesQuestion]); const { brandRules } = await inquirer.prompt([brandRulesQuestion]);
selectedBrandRules = brandRules; selectedBrandRules = brandRules;
const dryRun = options.dryRun || false; const dryRun = options.dryRun || false;
if (dryRun) { if (dryRun) {
@@ -435,12 +436,12 @@ let selectedBrandRules = options.rules && Array.isArray(options.rules) && option
// Create structure using only necessary values // Create structure using only necessary values
createProjectStructure(addAliasesPrompted, dryRun, selectedBrandRules); createProjectStructure(addAliasesPrompted, dryRun, selectedBrandRules);
for (const rule of selectedBrandRules) { for (const rule of selectedBrandRules) {
const profile = ruleProfiles[rule]; const profile = ruleProfiles[rule];
if (profile) { if (profile) {
convertAllRulesToBrandRules(targetDir, profile); convertAllRulesToBrandRules(targetDir, profile);
// Ensure MCP config is set up under the correct brand folder // Ensure MCP config is set up under the correct brand folder
if (rule === 'windsurf' || rule === 'roo') { if (rule === 'windsurf' || rule === 'roo') {
} }
} else { } else {
log('warn', `Unknown rules profile: ${rule}`); log('warn', `Unknown rules profile: ${rule}`);
@@ -468,7 +469,11 @@ function promptQuestion(rl, question) {
} }
// Function to create the project structure // Function to create the project structure
function createProjectStructure(addAliases, dryRun, selectedBrandRules = ['cursor']) { function createProjectStructure(
addAliases,
dryRun,
selectedBrandRules = ['cursor']
) {
const targetDir = process.cwd(); const targetDir = process.cwd();
log('info', `Initializing project in ${targetDir}`); log('info', `Initializing project in ${targetDir}`);

View File

@@ -8,66 +8,71 @@ import path from 'path';
// Mock logger // Mock logger
const mockLogger = { const mockLogger = {
info: jest.fn(), info: jest.fn(),
error: jest.fn(), error: jest.fn(),
warn: jest.fn(), warn: jest.fn(),
debug: jest.fn() debug: jest.fn()
}; };
// Use a temp directory for testing (simulate a project root) // Use a temp directory for testing (simulate a project root)
const tempProjectRoot = path.join(__dirname, '../../fixtures/temp-rules-project'); const tempProjectRoot = path.join(
__dirname,
'../../fixtures/temp-rules-project'
);
beforeAll(() => { beforeAll(() => {
if (!fs.existsSync(tempProjectRoot)) fs.mkdirSync(tempProjectRoot, { recursive: true }); if (!fs.existsSync(tempProjectRoot))
fs.mkdirSync(tempProjectRoot, { recursive: true });
}); });
afterAll(() => { afterAll(() => {
if (fs.existsSync(tempProjectRoot)) fs.rmSync(tempProjectRoot, { recursive: true, force: true }); if (fs.existsSync(tempProjectRoot))
fs.rmSync(tempProjectRoot, { recursive: true, force: true });
}); });
describe('rulesDirect (integration)', () => { describe('rulesDirect (integration)', () => {
it('should add brand rules successfully', async () => { it('should add brand rules successfully', async () => {
const args = { const args = {
action: 'add', action: 'add',
rules: ['roo'], rules: ['roo'],
projectRoot: tempProjectRoot, projectRoot: tempProjectRoot,
yes: true yes: true
}; };
const result = await rulesDirect(args, mockLogger, {}); const result = await rulesDirect(args, mockLogger, {});
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.data.output).toMatch(/add|roo/i); expect(result.data.output).toMatch(/add|roo/i);
}); });
it('should remove brand rules successfully', async () => { it('should remove brand rules successfully', async () => {
const args = { const args = {
action: 'remove', action: 'remove',
rules: ['roo'], rules: ['roo'],
projectRoot: tempProjectRoot, projectRoot: tempProjectRoot,
yes: true yes: true
}; };
const result = await rulesDirect(args, mockLogger, {}); const result = await rulesDirect(args, mockLogger, {});
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.data.output).toMatch(/remove|roo/i); expect(result.data.output).toMatch(/remove|roo/i);
}); });
it('should fail if missing required arguments', async () => { it('should fail if missing required arguments', async () => {
const args = { const args = {
action: 'add', action: 'add',
rules: [], // missing brands rules: [], // missing brands
projectRoot: tempProjectRoot projectRoot: tempProjectRoot
}; };
const result = await rulesDirect(args, mockLogger, {}); const result = await rulesDirect(args, mockLogger, {});
expect(result.success).toBe(false); expect(result.success).toBe(false);
expect(result.error.code).toBe('MISSING_ARGUMENT'); expect(result.error.code).toBe('MISSING_ARGUMENT');
}); });
it('should fail if projectRoot is missing', async () => { it('should fail if projectRoot is missing', async () => {
const args = { const args = {
action: 'add', action: 'add',
rules: ['roo'] rules: ['roo']
}; };
const result = await rulesDirect(args, mockLogger, {}); const result = await rulesDirect(args, mockLogger, {});
expect(result.success).toBe(false); expect(result.success).toBe(false);
expect(result.error.code).toBe('MISSING_ARGUMENT'); expect(result.error.code).toBe('MISSING_ARGUMENT');
}); });
}); });