fix formatting
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}`);
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user