mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2026-01-30 06:12:05 +00:00
feat: implement local-only command checkers for cli and mcp (#1426)
This commit is contained in:
@@ -1,21 +1,28 @@
|
|||||||
import boxen from 'boxen';
|
import boxen from 'boxen';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Level/variant for the card box styling
|
||||||
|
*/
|
||||||
|
export type CardBoxLevel = 'warn' | 'info';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration for the card box component
|
* Configuration for the card box component
|
||||||
*/
|
*/
|
||||||
export interface CardBoxConfig {
|
export interface CardBoxConfig {
|
||||||
/** Header text displayed in yellow bold */
|
/** Header text displayed in bold */
|
||||||
header: string;
|
header: string;
|
||||||
/** Body paragraphs displayed in white */
|
/** Body paragraphs displayed in white */
|
||||||
body: string[];
|
body: string[];
|
||||||
/** Call to action section with label and URL */
|
/** Call to action section with label and URL (optional) */
|
||||||
callToAction: {
|
callToAction?: {
|
||||||
label: string;
|
label: string;
|
||||||
action: string;
|
action: string;
|
||||||
};
|
};
|
||||||
/** Footer text displayed in gray (usage instructions) */
|
/** Footer text displayed in gray (usage instructions) */
|
||||||
footer?: string;
|
footer?: string;
|
||||||
|
/** Level/variant for styling (default: 'warn' = yellow, 'info' = blue) */
|
||||||
|
level?: CardBoxLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,22 +33,30 @@ export interface CardBoxConfig {
|
|||||||
* @returns Formatted string ready for console.log
|
* @returns Formatted string ready for console.log
|
||||||
*/
|
*/
|
||||||
export function displayCardBox(config: CardBoxConfig): string {
|
export function displayCardBox(config: CardBoxConfig): string {
|
||||||
const { header, body, callToAction, footer } = config;
|
const { header, body, callToAction, footer, level = 'warn' } = config;
|
||||||
|
|
||||||
|
// Determine colors based on level
|
||||||
|
const headerColor = level === 'info' ? chalk.blue.bold : chalk.yellow.bold;
|
||||||
|
const borderColor = level === 'info' ? 'blue' : 'yellow';
|
||||||
|
|
||||||
// Build the content sections
|
// Build the content sections
|
||||||
const sections: string[] = [
|
const sections: string[] = [
|
||||||
// Header
|
// Header
|
||||||
chalk.yellow.bold(header),
|
headerColor(header),
|
||||||
|
|
||||||
// Body paragraphs
|
// Body paragraphs
|
||||||
...body.map((paragraph) => chalk.white(paragraph)),
|
...body.map((paragraph) => chalk.white(paragraph))
|
||||||
|
|
||||||
// Call to action
|
|
||||||
chalk.cyan(callToAction.label) +
|
|
||||||
'\n' +
|
|
||||||
chalk.blue.underline(callToAction.action)
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add call to action if provided
|
||||||
|
if (callToAction && callToAction.label && callToAction.action) {
|
||||||
|
sections.push(
|
||||||
|
chalk.cyan(callToAction.label) +
|
||||||
|
'\n' +
|
||||||
|
chalk.blue.underline(callToAction.action)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Add footer if provided
|
// Add footer if provided
|
||||||
if (footer) {
|
if (footer) {
|
||||||
sections.push(chalk.gray(footer));
|
sections.push(chalk.gray(footer));
|
||||||
@@ -53,7 +68,7 @@ export function displayCardBox(config: CardBoxConfig): string {
|
|||||||
// Wrap in boxen
|
// Wrap in boxen
|
||||||
return boxen(content, {
|
return boxen(content, {
|
||||||
padding: 1,
|
padding: 1,
|
||||||
borderColor: 'yellow',
|
borderColor,
|
||||||
borderStyle: 'round',
|
borderStyle: 'round',
|
||||||
margin: { top: 1, bottom: 1 }
|
margin: { top: 1, bottom: 1 }
|
||||||
});
|
});
|
||||||
|
|||||||
144
apps/cli/src/utils/command-guard.ts
Normal file
144
apps/cli/src/utils/command-guard.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Command guard for CLI commands
|
||||||
|
* CLI presentation layer - uses tm-core for logic, displays with cardBox
|
||||||
|
*/
|
||||||
|
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { createTmCore, type TmCore, type LocalOnlyCommand } from '@tm/core';
|
||||||
|
import { displayCardBox } from '../ui/components/cardBox.component.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command-specific messaging configuration
|
||||||
|
*/
|
||||||
|
interface CommandMessage {
|
||||||
|
header: string;
|
||||||
|
getBody: (briefName: string) => string[];
|
||||||
|
footer: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get command-specific message configuration
|
||||||
|
*
|
||||||
|
* NOTE: Command groups below are intentionally hardcoded (not imported from LOCAL_ONLY_COMMANDS)
|
||||||
|
* to allow flexible categorization with custom messaging per category. All commands here are
|
||||||
|
* subsets of LOCAL_ONLY_COMMANDS from @tm/core, which is the source of truth for blocked commands.
|
||||||
|
*
|
||||||
|
* Categories exist for UX purposes (tailored messaging), while LOCAL_ONLY_COMMANDS exists for
|
||||||
|
* enforcement (what's actually blocked when using Hamster).
|
||||||
|
*/
|
||||||
|
function getCommandMessage(commandName: LocalOnlyCommand): CommandMessage {
|
||||||
|
// Dependency management commands
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
'add-dependency',
|
||||||
|
'remove-dependency',
|
||||||
|
'validate-dependencies',
|
||||||
|
'fix-dependencies'
|
||||||
|
].includes(commandName)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
header: 'Hamster Manages Dependencies',
|
||||||
|
getBody: (briefName) => [
|
||||||
|
`Hamster handles dependencies for the ${chalk.blue(`"${briefName}"`)} Brief.`,
|
||||||
|
`To manage dependencies manually, log out with ${chalk.cyan('tm auth logout')} and work locally.`
|
||||||
|
],
|
||||||
|
footer:
|
||||||
|
'Switch between local and remote workflows anytime by logging in/out.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtask management commands
|
||||||
|
if (commandName === 'clear-subtasks') {
|
||||||
|
return {
|
||||||
|
header: 'Hamster Manages Subtasks',
|
||||||
|
getBody: (briefName) => [
|
||||||
|
`Hamster handles subtask management for the ${chalk.blue(`"${briefName}"`)} Brief.`,
|
||||||
|
`To manage subtasks manually, log out with ${chalk.cyan('tm auth logout')} and work locally.`
|
||||||
|
],
|
||||||
|
footer:
|
||||||
|
'Switch between local and remote workflows anytime by logging in/out.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model configuration commands
|
||||||
|
if (commandName === 'models') {
|
||||||
|
return {
|
||||||
|
header: 'Hamster Manages AI Models',
|
||||||
|
getBody: (briefName) => [
|
||||||
|
`Hamster configures AI models automatically for the ${chalk.blue(`"${briefName}"`)} Brief.`,
|
||||||
|
`To configure models manually, log out with ${chalk.cyan('tm auth logout')} and work locally.`
|
||||||
|
],
|
||||||
|
footer:
|
||||||
|
'Switch between local and remote workflows anytime by logging in/out.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default message for any other local-only commands
|
||||||
|
return {
|
||||||
|
header: 'Command Not Available in Hamster',
|
||||||
|
getBody: (briefName) => [
|
||||||
|
`The ${chalk.cyan(commandName)} command is managed by Hamster for the ${chalk.blue(`"${briefName}"`)} Brief.`,
|
||||||
|
`To use this command, log out with ${chalk.cyan('tm auth logout')} and work locally.`
|
||||||
|
],
|
||||||
|
footer:
|
||||||
|
'Switch between local and remote workflows anytime by logging in/out.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a command should be blocked when authenticated and display CLI message
|
||||||
|
*
|
||||||
|
* Use this for CLI commands that are only available for local file storage (not Hamster).
|
||||||
|
* This uses tm-core's AuthDomain.guardCommand() and formats the result for CLI display.
|
||||||
|
*
|
||||||
|
* @param commandName - Name of the command being executed
|
||||||
|
* @param tmCoreOrProjectRoot - TmCore instance or project root path
|
||||||
|
* @returns true if command should be blocked, false otherwise
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const isBlocked = await checkAndBlockIfAuthenticated('add-dependency', projectRoot);
|
||||||
|
* if (isBlocked) {
|
||||||
|
* process.exit(1);
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export async function checkAndBlockIfAuthenticated(
|
||||||
|
commandName: string,
|
||||||
|
tmCoreOrProjectRoot: TmCore | string
|
||||||
|
): Promise<boolean> {
|
||||||
|
// Get or create TmCore instance
|
||||||
|
const tmCore =
|
||||||
|
typeof tmCoreOrProjectRoot === 'string'
|
||||||
|
? await createTmCore({ projectPath: tmCoreOrProjectRoot })
|
||||||
|
: tmCoreOrProjectRoot;
|
||||||
|
|
||||||
|
// Use tm-core's auth domain to check the command guard
|
||||||
|
const result = await tmCore.auth.guardCommand(
|
||||||
|
commandName,
|
||||||
|
tmCore.tasks.getStorageType()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.isBlocked) {
|
||||||
|
// Get command-specific message configuration
|
||||||
|
// Safe to cast: guardCommand only blocks commands in LOCAL_ONLY_COMMANDS
|
||||||
|
const message = getCommandMessage(commandName as LocalOnlyCommand);
|
||||||
|
const briefName = result.briefName || 'remote brief';
|
||||||
|
|
||||||
|
// Format and display CLI message with cardBox
|
||||||
|
console.log(
|
||||||
|
displayCardBox({
|
||||||
|
header: message.header,
|
||||||
|
body: message.getBody(briefName),
|
||||||
|
footer: message.footer,
|
||||||
|
level: 'info'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy export for backward compatibility
|
||||||
|
export const checkAndBlockDependencyCommand = checkAndBlockIfAuthenticated;
|
||||||
@@ -12,6 +12,12 @@ export {
|
|||||||
type CheckAuthOptions
|
type CheckAuthOptions
|
||||||
} from './auth-helpers.js';
|
} from './auth-helpers.js';
|
||||||
|
|
||||||
|
// Command guard for local-only commands
|
||||||
|
export {
|
||||||
|
checkAndBlockIfAuthenticated,
|
||||||
|
checkAndBlockDependencyCommand // Legacy export
|
||||||
|
} from './command-guard.js';
|
||||||
|
|
||||||
// Error handling utilities
|
// Error handling utilities
|
||||||
export { displayError, isDebugMode } from './error-handler.js';
|
export { displayError, isDebugMode } from './error-handler.js';
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,7 @@
|
|||||||
|
|
||||||
export * from './tools/autopilot/index.js';
|
export * from './tools/autopilot/index.js';
|
||||||
export * from './tools/tasks/index.js';
|
export * from './tools/tasks/index.js';
|
||||||
|
// TODO: Re-enable when TypeScript dependency tools are implemented
|
||||||
|
// export * from './tools/dependencies/index.js';
|
||||||
export * from './shared/utils.js';
|
export * from './shared/utils.js';
|
||||||
export * from './shared/types.js';
|
export * from './shared/types.js';
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* Shared types for MCP tools
|
* Shared types for MCP tools
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { TmCore } from '@tm/core';
|
||||||
|
|
||||||
export interface MCPResponse<T = any> {
|
export interface MCPResponse<T = any> {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data?: T;
|
data?: T;
|
||||||
@@ -31,6 +33,29 @@ export interface MCPContext {
|
|||||||
session: any;
|
session: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhanced MCP context with tmCore instance
|
||||||
|
*/
|
||||||
|
export interface ToolContext {
|
||||||
|
/** Logger instance (matches fastmcp's Context.log signature) */
|
||||||
|
log: {
|
||||||
|
info: (message: string, data?: any) => void;
|
||||||
|
warn: (message: string, data?: any) => void;
|
||||||
|
error: (message: string, data?: any) => void;
|
||||||
|
debug: (message: string, data?: any) => void;
|
||||||
|
};
|
||||||
|
/** MCP session */
|
||||||
|
session?: {
|
||||||
|
roots?: Array<{ uri: string; name?: string }>;
|
||||||
|
env?: Record<string, string>;
|
||||||
|
clientCapabilities?: {
|
||||||
|
sampling?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** TmCore instance (already initialized) */
|
||||||
|
tmCore: TmCore;
|
||||||
|
}
|
||||||
|
|
||||||
export interface WithProjectRoot {
|
export interface WithProjectRoot {
|
||||||
projectRoot: string;
|
projectRoot: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,16 @@
|
|||||||
* Shared utilities for MCP tools
|
* Shared utilities for MCP tools
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ContentResult } from 'fastmcp';
|
|
||||||
import path from 'node:path';
|
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import {
|
||||||
|
LOCAL_ONLY_COMMANDS,
|
||||||
|
createTmCore,
|
||||||
|
type LocalOnlyCommand
|
||||||
|
} from '@tm/core';
|
||||||
|
import type { ContentResult, Context } from 'fastmcp';
|
||||||
import packageJson from '../../../../package.json' with { type: 'json' };
|
import packageJson from '../../../../package.json' with { type: 'json' };
|
||||||
|
import type { ToolContext } from './types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get version information
|
* Get version information
|
||||||
@@ -17,6 +23,82 @@ export function getVersionInfo() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a content response for MCP tools
|
||||||
|
* FastMCP requires text type, so we format objects as JSON strings
|
||||||
|
*/
|
||||||
|
export function createContentResponse(content: any): ContentResult {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text:
|
||||||
|
typeof content === 'object'
|
||||||
|
? // Format JSON nicely with indentation
|
||||||
|
JSON.stringify(content, null, 2)
|
||||||
|
: // Keep other content types as-is
|
||||||
|
String(content)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an error response for MCP tools
|
||||||
|
*/
|
||||||
|
export function createErrorResponse(
|
||||||
|
errorMessage: string,
|
||||||
|
versionInfo?: { version: string; name: string },
|
||||||
|
tagInfo?: { currentTag: string }
|
||||||
|
): ContentResult {
|
||||||
|
// Provide fallback version info if not provided
|
||||||
|
if (!versionInfo) {
|
||||||
|
versionInfo = getVersionInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
let responseText = `Error: ${errorMessage}
|
||||||
|
Version: ${versionInfo.version}
|
||||||
|
Name: ${versionInfo.name}`;
|
||||||
|
|
||||||
|
// Add tag information if available
|
||||||
|
if (tagInfo) {
|
||||||
|
responseText += `
|
||||||
|
Current Tag: ${tagInfo.currentTag}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: responseText
|
||||||
|
}
|
||||||
|
],
|
||||||
|
isError: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function signature for progress reporting
|
||||||
|
*/
|
||||||
|
export type ReportProgressFn = (progress: number, total?: number) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that reportProgress is available for long-running operations
|
||||||
|
*/
|
||||||
|
export function checkProgressCapability(
|
||||||
|
reportProgress: any,
|
||||||
|
log: any
|
||||||
|
): ReportProgressFn | undefined {
|
||||||
|
if (typeof reportProgress !== 'function') {
|
||||||
|
log?.debug?.(
|
||||||
|
'reportProgress not available - operation will run without progress updates'
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reportProgress;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current tag for a project root
|
* Get current tag for a project root
|
||||||
*/
|
*/
|
||||||
@@ -183,7 +265,7 @@ function getProjectRootFromSession(session: any): string | null {
|
|||||||
export function withNormalizedProjectRoot<T extends { projectRoot?: string }>(
|
export function withNormalizedProjectRoot<T extends { projectRoot?: string }>(
|
||||||
fn: (
|
fn: (
|
||||||
args: T & { projectRoot: string },
|
args: T & { projectRoot: string },
|
||||||
context: any
|
context: Context<undefined>
|
||||||
) => Promise<ContentResult>
|
) => Promise<ContentResult>
|
||||||
): (args: T, context: any) => Promise<ContentResult> {
|
): (args: T, context: any) => Promise<ContentResult> {
|
||||||
return async (args: T, context: any): Promise<ContentResult> => {
|
return async (args: T, context: any): Promise<ContentResult> => {
|
||||||
@@ -268,3 +350,87 @@ export function withNormalizedProjectRoot<T extends { projectRoot?: string }>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool execution function signature with tmCore provided
|
||||||
|
*/
|
||||||
|
export type ToolExecuteFn<TArgs = any, TResult = any> = (
|
||||||
|
args: TArgs,
|
||||||
|
context: ToolContext
|
||||||
|
) => Promise<TResult>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Higher-order function that wraps MCP tool execution with:
|
||||||
|
* - Normalized project root (via withNormalizedProjectRoot)
|
||||||
|
* - TmCore instance creation
|
||||||
|
* - Command guard check (for local-only commands)
|
||||||
|
*
|
||||||
|
* Use this for ALL MCP tools to provide consistent context and auth checking.
|
||||||
|
*
|
||||||
|
* @param commandName - Name of the command (used for guard check)
|
||||||
|
* @param executeFn - Tool execution function that receives args and enhanced context
|
||||||
|
* @returns Wrapped execute function
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* export function registerAddDependencyTool(server: FastMCP) {
|
||||||
|
* server.addTool({
|
||||||
|
* name: 'add_dependency',
|
||||||
|
* parameters: AddDependencySchema,
|
||||||
|
* execute: withToolContext('add-dependency', async (args, context) => {
|
||||||
|
* // context.tmCore is already available
|
||||||
|
* // Auth guard already checked
|
||||||
|
* // Just implement the tool logic!
|
||||||
|
* })
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function withToolContext<TArgs extends { projectRoot?: string }>(
|
||||||
|
commandName: string,
|
||||||
|
executeFn: ToolExecuteFn<TArgs & { projectRoot: string }, ContentResult>
|
||||||
|
) {
|
||||||
|
return withNormalizedProjectRoot(
|
||||||
|
async (
|
||||||
|
args: TArgs & { projectRoot: string },
|
||||||
|
context: Context<undefined>
|
||||||
|
) => {
|
||||||
|
// Create tmCore instance
|
||||||
|
const tmCore = await createTmCore({
|
||||||
|
projectPath: args.projectRoot,
|
||||||
|
loggerConfig: { mcpMode: true, logCallback: context.log }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if this is a local-only command that needs auth guard
|
||||||
|
if (LOCAL_ONLY_COMMANDS.includes(commandName as LocalOnlyCommand)) {
|
||||||
|
const authResult = await tmCore.auth.guardCommand(
|
||||||
|
commandName,
|
||||||
|
tmCore.tasks.getStorageType()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (authResult.isBlocked) {
|
||||||
|
const errorMsg = `You're working on the ${authResult.briefName} Brief in Hamster so this command is managed for you. This command is only available for local file storage. Log out with 'tm auth logout' to use local commands.`;
|
||||||
|
context.log.info(errorMsg);
|
||||||
|
return handleApiResult({
|
||||||
|
result: {
|
||||||
|
success: false,
|
||||||
|
error: { message: errorMsg }
|
||||||
|
},
|
||||||
|
log: context.log,
|
||||||
|
projectRoot: args.projectRoot
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create enhanced context with tmCore
|
||||||
|
const enhancedContext: ToolContext = {
|
||||||
|
log: context.log,
|
||||||
|
session: context.session,
|
||||||
|
tmCore
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute the actual tool logic with enhanced context
|
||||||
|
return executeFn(args, enhancedContext);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,14 +3,11 @@
|
|||||||
* Abort a running TDD workflow and clean up state
|
* Abort a running TDD workflow and clean up state
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
|
||||||
import {
|
|
||||||
handleApiResult,
|
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from '../../shared/utils.js';
|
|
||||||
import type { MCPContext } from '../../shared/types.js';
|
|
||||||
import { WorkflowService } from '@tm/core';
|
import { WorkflowService } from '@tm/core';
|
||||||
import type { FastMCP } from 'fastmcp';
|
import type { FastMCP } from 'fastmcp';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import type { ToolContext } from '../../shared/types.js';
|
||||||
|
import { handleApiResult, withToolContext } from '../../shared/utils.js';
|
||||||
|
|
||||||
const AbortSchema = z.object({
|
const AbortSchema = z.object({
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
@@ -29,12 +26,13 @@ export function registerAutopilotAbortTool(server: FastMCP) {
|
|||||||
description:
|
description:
|
||||||
'Abort the current TDD workflow and clean up workflow state. This will remove the workflow state file but will NOT delete the git branch or any code changes.',
|
'Abort the current TDD workflow and clean up workflow state. This will remove the workflow state file but will NOT delete the git branch or any code changes.',
|
||||||
parameters: AbortSchema,
|
parameters: AbortSchema,
|
||||||
execute: withNormalizedProjectRoot(
|
execute: withToolContext(
|
||||||
async (args: AbortArgs, context: MCPContext) => {
|
'autopilot-abort',
|
||||||
|
async (args: AbortArgs, { log }: ToolContext) => {
|
||||||
const { projectRoot } = args;
|
const { projectRoot } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.log.info(`Aborting autopilot workflow in ${projectRoot}`);
|
log.info(`Aborting autopilot workflow in ${projectRoot}`);
|
||||||
|
|
||||||
const workflowService = new WorkflowService(projectRoot);
|
const workflowService = new WorkflowService(projectRoot);
|
||||||
|
|
||||||
@@ -42,7 +40,7 @@ export function registerAutopilotAbortTool(server: FastMCP) {
|
|||||||
const hasWorkflow = await workflowService.hasWorkflow();
|
const hasWorkflow = await workflowService.hasWorkflow();
|
||||||
|
|
||||||
if (!hasWorkflow) {
|
if (!hasWorkflow) {
|
||||||
context.log.warn('No active workflow to abort');
|
log.warn('No active workflow to abort');
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -51,7 +49,7 @@ export function registerAutopilotAbortTool(server: FastMCP) {
|
|||||||
hadWorkflow: false
|
hadWorkflow: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -63,7 +61,7 @@ export function registerAutopilotAbortTool(server: FastMCP) {
|
|||||||
// Abort workflow
|
// Abort workflow
|
||||||
await workflowService.abortWorkflow();
|
await workflowService.abortWorkflow();
|
||||||
|
|
||||||
context.log.info('Workflow state deleted');
|
log.info('Workflow state deleted');
|
||||||
|
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
@@ -76,20 +74,20 @@ export function registerAutopilotAbortTool(server: FastMCP) {
|
|||||||
note: 'Git branch and code changes were preserved. You can manually clean them up if needed.'
|
note: 'Git branch and code changes were preserved. You can manually clean them up if needed.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Error in autopilot-abort: ${error.message}`);
|
log.error(`Error in autopilot-abort: ${error.message}`);
|
||||||
if (error.stack) {
|
if (error.stack) {
|
||||||
context.log.debug(error.stack);
|
log.debug(error.stack);
|
||||||
}
|
}
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
error: { message: `Failed to abort workflow: ${error.message}` }
|
error: { message: `Failed to abort workflow: ${error.message}` }
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,11 @@
|
|||||||
* Create a git commit with automatic staging and message generation
|
* Create a git commit with automatic staging and message generation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { CommitMessageGenerator, GitAdapter, WorkflowService } from '@tm/core';
|
||||||
import {
|
|
||||||
handleApiResult,
|
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from '../../shared/utils.js';
|
|
||||||
import type { MCPContext } from '../../shared/types.js';
|
|
||||||
import { WorkflowService, GitAdapter, CommitMessageGenerator } from '@tm/core';
|
|
||||||
import type { FastMCP } from 'fastmcp';
|
import type { FastMCP } from 'fastmcp';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import type { ToolContext } from '../../shared/types.js';
|
||||||
|
import { handleApiResult, withToolContext } from '../../shared/utils.js';
|
||||||
|
|
||||||
const CommitSchema = z.object({
|
const CommitSchema = z.object({
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
@@ -39,12 +36,13 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
description:
|
description:
|
||||||
'Create a git commit with automatic staging, message generation, and metadata embedding. Generates appropriate commit messages based on subtask context and TDD phase.',
|
'Create a git commit with automatic staging, message generation, and metadata embedding. Generates appropriate commit messages based on subtask context and TDD phase.',
|
||||||
parameters: CommitSchema,
|
parameters: CommitSchema,
|
||||||
execute: withNormalizedProjectRoot(
|
execute: withToolContext(
|
||||||
async (args: CommitArgs, context: MCPContext) => {
|
'autopilot-commit',
|
||||||
|
async (args: CommitArgs, { log }: ToolContext) => {
|
||||||
const { projectRoot, files, customMessage } = args;
|
const { projectRoot, files, customMessage } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.log.info(`Creating commit for workflow in ${projectRoot}`);
|
log.info(`Creating commit for workflow in ${projectRoot}`);
|
||||||
|
|
||||||
const workflowService = new WorkflowService(projectRoot);
|
const workflowService = new WorkflowService(projectRoot);
|
||||||
|
|
||||||
@@ -58,7 +56,7 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
'No active workflow found. Start a workflow with autopilot_start'
|
'No active workflow found. Start a workflow with autopilot_start'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -70,9 +68,7 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
|
|
||||||
// Verify we're in COMMIT phase
|
// Verify we're in COMMIT phase
|
||||||
if (status.tddPhase !== 'COMMIT') {
|
if (status.tddPhase !== 'COMMIT') {
|
||||||
context.log.warn(
|
log.warn(`Not in COMMIT phase (currently in ${status.tddPhase})`);
|
||||||
`Not in COMMIT phase (currently in ${status.tddPhase})`
|
|
||||||
);
|
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -80,7 +76,7 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
message: `Cannot commit: currently in ${status.tddPhase} phase. Complete the ${status.tddPhase} phase first using autopilot_complete_phase`
|
message: `Cannot commit: currently in ${status.tddPhase} phase. Complete the ${status.tddPhase} phase first using autopilot_complete_phase`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -92,7 +88,7 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
success: false,
|
success: false,
|
||||||
error: { message: 'No active subtask to commit' }
|
error: { message: 'No active subtask to commit' }
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -104,19 +100,19 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
try {
|
try {
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
await gitAdapter.stageFiles(files);
|
await gitAdapter.stageFiles(files);
|
||||||
context.log.info(`Staged ${files.length} files`);
|
log.info(`Staged ${files.length} files`);
|
||||||
} else {
|
} else {
|
||||||
await gitAdapter.stageFiles(['.']);
|
await gitAdapter.stageFiles(['.']);
|
||||||
context.log.info('Staged all changes');
|
log.info('Staged all changes');
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Failed to stage files: ${error.message}`);
|
log.error(`Failed to stage files: ${error.message}`);
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
error: { message: `Failed to stage files: ${error.message}` }
|
error: { message: `Failed to stage files: ${error.message}` }
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -124,7 +120,7 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
// Check if there are staged changes
|
// Check if there are staged changes
|
||||||
const hasStagedChanges = await gitAdapter.hasStagedChanges();
|
const hasStagedChanges = await gitAdapter.hasStagedChanges();
|
||||||
if (!hasStagedChanges) {
|
if (!hasStagedChanges) {
|
||||||
context.log.warn('No staged changes to commit');
|
log.warn('No staged changes to commit');
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -133,7 +129,7 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
'No staged changes to commit. Make code changes before committing'
|
'No staged changes to commit. Make code changes before committing'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -145,7 +141,7 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
let commitMessage: string;
|
let commitMessage: string;
|
||||||
if (customMessage) {
|
if (customMessage) {
|
||||||
commitMessage = customMessage;
|
commitMessage = customMessage;
|
||||||
context.log.info('Using custom commit message');
|
log.info('Using custom commit message');
|
||||||
} else {
|
} else {
|
||||||
const messageGenerator = new CommitMessageGenerator();
|
const messageGenerator = new CommitMessageGenerator();
|
||||||
|
|
||||||
@@ -168,21 +164,21 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
commitMessage = messageGenerator.generateMessage(options);
|
commitMessage = messageGenerator.generateMessage(options);
|
||||||
context.log.info('Generated commit message automatically');
|
log.info('Generated commit message automatically');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create commit
|
// Create commit
|
||||||
try {
|
try {
|
||||||
await gitAdapter.createCommit(commitMessage);
|
await gitAdapter.createCommit(commitMessage);
|
||||||
context.log.info('Commit created successfully');
|
log.info('Commit created successfully');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Failed to create commit: ${error.message}`);
|
log.error(`Failed to create commit: ${error.message}`);
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
error: { message: `Failed to create commit: ${error.message}` }
|
error: { message: `Failed to create commit: ${error.message}` }
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -193,7 +189,7 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
// Complete COMMIT phase and advance workflow
|
// Complete COMMIT phase and advance workflow
|
||||||
const newStatus = await workflowService.commit();
|
const newStatus = await workflowService.commit();
|
||||||
|
|
||||||
context.log.info(
|
log.info(
|
||||||
`Commit completed. Current phase: ${newStatus.tddPhase || newStatus.phase}`
|
`Commit completed. Current phase: ${newStatus.tddPhase || newStatus.phase}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -217,20 +213,20 @@ export function registerAutopilotCommitTool(server: FastMCP) {
|
|||||||
nextSteps: nextAction.nextSteps
|
nextSteps: nextAction.nextSteps
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Error in autopilot-commit: ${error.message}`);
|
log.error(`Error in autopilot-commit: ${error.message}`);
|
||||||
if (error.stack) {
|
if (error.stack) {
|
||||||
context.log.debug(error.stack);
|
log.debug(error.stack);
|
||||||
}
|
}
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
error: { message: `Failed to commit: ${error.message}` }
|
error: { message: `Failed to commit: ${error.message}` }
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,11 @@
|
|||||||
* Complete the current TDD phase with test result validation
|
* Complete the current TDD phase with test result validation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
|
||||||
import {
|
|
||||||
handleApiResult,
|
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from '../../shared/utils.js';
|
|
||||||
import type { MCPContext } from '../../shared/types.js';
|
|
||||||
import { WorkflowService } from '@tm/core';
|
import { WorkflowService } from '@tm/core';
|
||||||
import type { FastMCP } from 'fastmcp';
|
import type { FastMCP } from 'fastmcp';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import type { ToolContext } from '../../shared/types.js';
|
||||||
|
import { handleApiResult, withToolContext } from '../../shared/utils.js';
|
||||||
|
|
||||||
const CompletePhaseSchema = z.object({
|
const CompletePhaseSchema = z.object({
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
@@ -37,14 +34,13 @@ export function registerAutopilotCompleteTool(server: FastMCP) {
|
|||||||
description:
|
description:
|
||||||
'Complete the current TDD phase (RED, GREEN, or COMMIT) with test result validation. RED phase: expects failures (if 0 failures, feature is already implemented and subtask auto-completes). GREEN phase: expects all tests passing.',
|
'Complete the current TDD phase (RED, GREEN, or COMMIT) with test result validation. RED phase: expects failures (if 0 failures, feature is already implemented and subtask auto-completes). GREEN phase: expects all tests passing.',
|
||||||
parameters: CompletePhaseSchema,
|
parameters: CompletePhaseSchema,
|
||||||
execute: withNormalizedProjectRoot(
|
execute: withToolContext(
|
||||||
async (args: CompletePhaseArgs, context: MCPContext) => {
|
'autopilot-complete-phase',
|
||||||
|
async (args: CompletePhaseArgs, { log }: ToolContext) => {
|
||||||
const { projectRoot, testResults } = args;
|
const { projectRoot, testResults } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.log.info(
|
log.info(`Completing current phase in workflow for ${projectRoot}`);
|
||||||
`Completing current phase in workflow for ${projectRoot}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const workflowService = new WorkflowService(projectRoot);
|
const workflowService = new WorkflowService(projectRoot);
|
||||||
|
|
||||||
@@ -58,7 +54,7 @@ export function registerAutopilotCompleteTool(server: FastMCP) {
|
|||||||
'No active workflow found. Start a workflow with autopilot_start'
|
'No active workflow found. Start a workflow with autopilot_start'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -76,7 +72,7 @@ export function registerAutopilotCompleteTool(server: FastMCP) {
|
|||||||
message: `Cannot complete phase: not in a TDD phase (current phase: ${currentStatus.phase})`
|
message: `Cannot complete phase: not in a TDD phase (current phase: ${currentStatus.phase})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -91,7 +87,7 @@ export function registerAutopilotCompleteTool(server: FastMCP) {
|
|||||||
'Cannot complete COMMIT phase with this tool. Use autopilot_commit instead'
|
'Cannot complete COMMIT phase with this tool. Use autopilot_commit instead'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -112,7 +108,7 @@ export function registerAutopilotCompleteTool(server: FastMCP) {
|
|||||||
const status = await workflowService.completePhase(fullTestResults);
|
const status = await workflowService.completePhase(fullTestResults);
|
||||||
const nextAction = workflowService.getNextAction();
|
const nextAction = workflowService.getNextAction();
|
||||||
|
|
||||||
context.log.info(
|
log.info(
|
||||||
`Phase completed. New phase: ${status.tddPhase || status.phase}`
|
`Phase completed. New phase: ${status.tddPhase || status.phase}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -127,13 +123,13 @@ export function registerAutopilotCompleteTool(server: FastMCP) {
|
|||||||
nextSteps: nextAction.nextSteps
|
nextSteps: nextAction.nextSteps
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Error in autopilot-complete: ${error.message}`);
|
log.error(`Error in autopilot-complete: ${error.message}`);
|
||||||
if (error.stack) {
|
if (error.stack) {
|
||||||
context.log.debug(error.stack);
|
log.debug(error.stack);
|
||||||
}
|
}
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
@@ -142,7 +138,7 @@ export function registerAutopilotCompleteTool(server: FastMCP) {
|
|||||||
message: `Failed to complete phase: ${error.message}`
|
message: `Failed to complete phase: ${error.message}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,11 @@
|
|||||||
* Finalize and complete the workflow with working tree validation
|
* Finalize and complete the workflow with working tree validation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
|
||||||
import {
|
|
||||||
handleApiResult,
|
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from '../../shared/utils.js';
|
|
||||||
import type { MCPContext } from '../../shared/types.js';
|
|
||||||
import { WorkflowService } from '@tm/core';
|
import { WorkflowService } from '@tm/core';
|
||||||
import type { FastMCP } from 'fastmcp';
|
import type { FastMCP } from 'fastmcp';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import type { ToolContext } from '../../shared/types.js';
|
||||||
|
import { handleApiResult, withToolContext } from '../../shared/utils.js';
|
||||||
|
|
||||||
const FinalizeSchema = z.object({
|
const FinalizeSchema = z.object({
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
@@ -29,12 +26,13 @@ export function registerAutopilotFinalizeTool(server: FastMCP) {
|
|||||||
description:
|
description:
|
||||||
'Finalize and complete the workflow. Validates that all changes are committed and working tree is clean before marking workflow as complete.',
|
'Finalize and complete the workflow. Validates that all changes are committed and working tree is clean before marking workflow as complete.',
|
||||||
parameters: FinalizeSchema,
|
parameters: FinalizeSchema,
|
||||||
execute: withNormalizedProjectRoot(
|
execute: withToolContext(
|
||||||
async (args: FinalizeArgs, context: MCPContext) => {
|
'autopilot-finalize',
|
||||||
|
async (args: FinalizeArgs, { log }: ToolContext) => {
|
||||||
const { projectRoot } = args;
|
const { projectRoot } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.log.info(`Finalizing workflow in ${projectRoot}`);
|
log.info(`Finalizing workflow in ${projectRoot}`);
|
||||||
|
|
||||||
const workflowService = new WorkflowService(projectRoot);
|
const workflowService = new WorkflowService(projectRoot);
|
||||||
|
|
||||||
@@ -48,7 +46,7 @@ export function registerAutopilotFinalizeTool(server: FastMCP) {
|
|||||||
'No active workflow found. Start a workflow with autopilot_start'
|
'No active workflow found. Start a workflow with autopilot_start'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -66,7 +64,7 @@ export function registerAutopilotFinalizeTool(server: FastMCP) {
|
|||||||
message: `Cannot finalize: workflow is in ${currentStatus.phase} phase. Complete all subtasks first.`
|
message: `Cannot finalize: workflow is in ${currentStatus.phase} phase. Complete all subtasks first.`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -74,7 +72,7 @@ export function registerAutopilotFinalizeTool(server: FastMCP) {
|
|||||||
// Finalize workflow (validates clean working tree)
|
// Finalize workflow (validates clean working tree)
|
||||||
const newStatus = await workflowService.finalizeWorkflow();
|
const newStatus = await workflowService.finalizeWorkflow();
|
||||||
|
|
||||||
context.log.info('Workflow finalized successfully');
|
log.info('Workflow finalized successfully');
|
||||||
|
|
||||||
// Get next action
|
// Get next action
|
||||||
const nextAction = workflowService.getNextAction();
|
const nextAction = workflowService.getNextAction();
|
||||||
@@ -89,13 +87,13 @@ export function registerAutopilotFinalizeTool(server: FastMCP) {
|
|||||||
nextSteps: nextAction.nextSteps
|
nextSteps: nextAction.nextSteps
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Error in autopilot-finalize: ${error.message}`);
|
log.error(`Error in autopilot-finalize: ${error.message}`);
|
||||||
if (error.stack) {
|
if (error.stack) {
|
||||||
context.log.debug(error.stack);
|
log.debug(error.stack);
|
||||||
}
|
}
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
@@ -104,7 +102,7 @@ export function registerAutopilotFinalizeTool(server: FastMCP) {
|
|||||||
message: `Failed to finalize workflow: ${error.message}`
|
message: `Failed to finalize workflow: ${error.message}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,11 @@
|
|||||||
* Get the next action to perform in the TDD workflow
|
* Get the next action to perform in the TDD workflow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
|
||||||
import {
|
|
||||||
handleApiResult,
|
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from '../../shared/utils.js';
|
|
||||||
import type { MCPContext } from '../../shared/types.js';
|
|
||||||
import { WorkflowService } from '@tm/core';
|
import { WorkflowService } from '@tm/core';
|
||||||
import type { FastMCP } from 'fastmcp';
|
import type { FastMCP } from 'fastmcp';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import type { ToolContext } from '../../shared/types.js';
|
||||||
|
import { handleApiResult, withToolContext } from '../../shared/utils.js';
|
||||||
|
|
||||||
const NextActionSchema = z.object({
|
const NextActionSchema = z.object({
|
||||||
projectRoot: z
|
projectRoot: z
|
||||||
@@ -29,14 +26,13 @@ export function registerAutopilotNextTool(server: FastMCP) {
|
|||||||
description:
|
description:
|
||||||
'Get the next action to perform in the TDD workflow. Returns detailed context about what needs to be done next, including the current phase, subtask, and expected actions.',
|
'Get the next action to perform in the TDD workflow. Returns detailed context about what needs to be done next, including the current phase, subtask, and expected actions.',
|
||||||
parameters: NextActionSchema,
|
parameters: NextActionSchema,
|
||||||
execute: withNormalizedProjectRoot(
|
execute: withToolContext(
|
||||||
async (args: NextActionArgs, context: MCPContext) => {
|
'autopilot-next',
|
||||||
|
async (args: NextActionArgs, { log }: ToolContext) => {
|
||||||
const { projectRoot } = args;
|
const { projectRoot } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.log.info(
|
log.info(`Getting next action for workflow in ${projectRoot}`);
|
||||||
`Getting next action for workflow in ${projectRoot}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const workflowService = new WorkflowService(projectRoot);
|
const workflowService = new WorkflowService(projectRoot);
|
||||||
|
|
||||||
@@ -50,7 +46,7 @@ export function registerAutopilotNextTool(server: FastMCP) {
|
|||||||
'No active workflow found. Start a workflow with autopilot_start'
|
'No active workflow found. Start a workflow with autopilot_start'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -62,7 +58,7 @@ export function registerAutopilotNextTool(server: FastMCP) {
|
|||||||
const nextAction = workflowService.getNextAction();
|
const nextAction = workflowService.getNextAction();
|
||||||
const status = workflowService.getStatus();
|
const status = workflowService.getStatus();
|
||||||
|
|
||||||
context.log.info(`Next action determined: ${nextAction.action}`);
|
log.info(`Next action determined: ${nextAction.action}`);
|
||||||
|
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
@@ -74,13 +70,13 @@ export function registerAutopilotNextTool(server: FastMCP) {
|
|||||||
nextSteps: nextAction.nextSteps
|
nextSteps: nextAction.nextSteps
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Error in autopilot-next: ${error.message}`);
|
log.error(`Error in autopilot-next: ${error.message}`);
|
||||||
if (error.stack) {
|
if (error.stack) {
|
||||||
context.log.debug(error.stack);
|
log.debug(error.stack);
|
||||||
}
|
}
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
@@ -89,7 +85,7 @@ export function registerAutopilotNextTool(server: FastMCP) {
|
|||||||
message: `Failed to get next action: ${error.message}`
|
message: `Failed to get next action: ${error.message}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import { handleApiResult, withToolContext } from '../../shared/utils.js';
|
||||||
handleApiResult,
|
import type { ToolContext } from '../../shared/types.js';
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from '../../shared/utils.js';
|
|
||||||
import type { MCPContext } from '../../shared/types.js';
|
|
||||||
import { WorkflowService } from '@tm/core';
|
import { WorkflowService } from '@tm/core';
|
||||||
import type { FastMCP } from 'fastmcp';
|
import type { FastMCP } from 'fastmcp';
|
||||||
|
|
||||||
@@ -29,12 +26,13 @@ export function registerAutopilotResumeTool(server: FastMCP) {
|
|||||||
description:
|
description:
|
||||||
'Resume a previously started TDD workflow from saved state. Restores the workflow state machine and continues from where it left off.',
|
'Resume a previously started TDD workflow from saved state. Restores the workflow state machine and continues from where it left off.',
|
||||||
parameters: ResumeWorkflowSchema,
|
parameters: ResumeWorkflowSchema,
|
||||||
execute: withNormalizedProjectRoot(
|
execute: withToolContext(
|
||||||
async (args: ResumeWorkflowArgs, context: MCPContext) => {
|
'autopilot-resume',
|
||||||
|
async (args: ResumeWorkflowArgs, { log }: ToolContext) => {
|
||||||
const { projectRoot } = args;
|
const { projectRoot } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.log.info(`Resuming autopilot workflow in ${projectRoot}`);
|
log.info(`Resuming autopilot workflow in ${projectRoot}`);
|
||||||
|
|
||||||
const workflowService = new WorkflowService(projectRoot);
|
const workflowService = new WorkflowService(projectRoot);
|
||||||
|
|
||||||
@@ -48,7 +46,7 @@ export function registerAutopilotResumeTool(server: FastMCP) {
|
|||||||
'No workflow state found. Start a new workflow with autopilot_start'
|
'No workflow state found. Start a new workflow with autopilot_start'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -57,9 +55,7 @@ export function registerAutopilotResumeTool(server: FastMCP) {
|
|||||||
const status = await workflowService.resumeWorkflow();
|
const status = await workflowService.resumeWorkflow();
|
||||||
const nextAction = workflowService.getNextAction();
|
const nextAction = workflowService.getNextAction();
|
||||||
|
|
||||||
context.log.info(
|
log.info(`Workflow resumed successfully for task ${status.taskId}`);
|
||||||
`Workflow resumed successfully for task ${status.taskId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
@@ -72,20 +68,20 @@ export function registerAutopilotResumeTool(server: FastMCP) {
|
|||||||
nextSteps: nextAction.nextSteps
|
nextSteps: nextAction.nextSteps
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Error in autopilot-resume: ${error.message}`);
|
log.error(`Error in autopilot-resume: ${error.message}`);
|
||||||
if (error.stack) {
|
if (error.stack) {
|
||||||
context.log.debug(error.stack);
|
log.debug(error.stack);
|
||||||
}
|
}
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
error: { message: `Failed to resume workflow: ${error.message}` }
|
error: { message: `Failed to resume workflow: ${error.message}` }
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import { handleApiResult, withToolContext } from '../../shared/utils.js';
|
||||||
handleApiResult,
|
import type { ToolContext } from '../../shared/types.js';
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from '../../shared/utils.js';
|
|
||||||
import type { MCPContext } from '../../shared/types.js';
|
|
||||||
import { createTmCore } from '@tm/core';
|
|
||||||
import { WorkflowService } from '@tm/core';
|
import { WorkflowService } from '@tm/core';
|
||||||
import type { FastMCP } from 'fastmcp';
|
import type { FastMCP } from 'fastmcp';
|
||||||
|
|
||||||
@@ -57,12 +53,13 @@ export function registerAutopilotStartTool(server: FastMCP) {
|
|||||||
description:
|
description:
|
||||||
'Initialize and start a new TDD workflow for a task. Creates a git branch and sets up the workflow state machine.',
|
'Initialize and start a new TDD workflow for a task. Creates a git branch and sets up the workflow state machine.',
|
||||||
parameters: StartWorkflowSchema,
|
parameters: StartWorkflowSchema,
|
||||||
execute: withNormalizedProjectRoot(
|
execute: withToolContext(
|
||||||
async (args: StartWorkflowArgs, context: MCPContext) => {
|
'autopilot-start',
|
||||||
|
async (args: StartWorkflowArgs, { log, tmCore }: ToolContext) => {
|
||||||
const { taskId, projectRoot, maxAttempts, force } = args;
|
const { taskId, projectRoot, maxAttempts, force } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.log.info(
|
log.info(
|
||||||
`Starting autopilot workflow for task ${taskId} in ${projectRoot}`
|
`Starting autopilot workflow for task ${taskId} in ${projectRoot}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -75,20 +72,15 @@ export function registerAutopilotStartTool(server: FastMCP) {
|
|||||||
message: `Task ID "${taskId}" is a subtask. Autopilot workflows can only be started for main tasks (e.g., "1", "2", "HAM-123"). Please provide the parent task ID instead.`
|
message: `Task ID "${taskId}" is a subtask. Autopilot workflows can only be started for main tasks (e.g., "1", "2", "HAM-123"). Please provide the parent task ID instead.`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load task data and get current tag
|
|
||||||
const core = await createTmCore({
|
|
||||||
projectPath: projectRoot
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get current tag from ConfigManager
|
// Get current tag from ConfigManager
|
||||||
const currentTag = core.config.getActiveTag();
|
const currentTag = tmCore.config.getActiveTag();
|
||||||
|
|
||||||
const taskResult = await core.tasks.get(taskId);
|
const taskResult = await tmCore.tasks.get(taskId);
|
||||||
|
|
||||||
if (!taskResult || !taskResult.task) {
|
if (!taskResult || !taskResult.task) {
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
@@ -96,7 +88,7 @@ export function registerAutopilotStartTool(server: FastMCP) {
|
|||||||
success: false,
|
success: false,
|
||||||
error: { message: `Task ${taskId} not found` }
|
error: { message: `Task ${taskId} not found` }
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -112,7 +104,7 @@ export function registerAutopilotStartTool(server: FastMCP) {
|
|||||||
message: `Task ${taskId} has no subtasks. Please use expand_task (with id="${taskId}") to create subtasks first. For improved results, consider running analyze_complexity before expanding the task.`
|
message: `Task ${taskId} has no subtasks. Please use expand_task (with id="${taskId}") to create subtasks first. For improved results, consider running analyze_complexity before expanding the task.`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -123,7 +115,7 @@ export function registerAutopilotStartTool(server: FastMCP) {
|
|||||||
// Check for existing workflow
|
// Check for existing workflow
|
||||||
const hasWorkflow = await workflowService.hasWorkflow();
|
const hasWorkflow = await workflowService.hasWorkflow();
|
||||||
if (hasWorkflow && !force) {
|
if (hasWorkflow && !force) {
|
||||||
context.log.warn('Workflow state already exists');
|
log.warn('Workflow state already exists');
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -132,7 +124,7 @@ export function registerAutopilotStartTool(server: FastMCP) {
|
|||||||
'Workflow already in progress. Use force=true to override or resume the existing workflow. Suggestion: Use autopilot_resume to continue the existing workflow'
|
'Workflow already in progress. Use force=true to override or resume the existing workflow. Suggestion: Use autopilot_resume to continue the existing workflow'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -152,7 +144,7 @@ export function registerAutopilotStartTool(server: FastMCP) {
|
|||||||
tag: currentTag // Pass current tag for branch naming
|
tag: currentTag // Pass current tag for branch naming
|
||||||
});
|
});
|
||||||
|
|
||||||
context.log.info(`Workflow started successfully for task ${taskId}`);
|
log.info(`Workflow started successfully for task ${taskId}`);
|
||||||
|
|
||||||
// Get next action with guidance from WorkflowService
|
// Get next action with guidance from WorkflowService
|
||||||
const nextAction = workflowService.getNextAction();
|
const nextAction = workflowService.getNextAction();
|
||||||
@@ -172,20 +164,20 @@ export function registerAutopilotStartTool(server: FastMCP) {
|
|||||||
nextSteps: nextAction.nextSteps
|
nextSteps: nextAction.nextSteps
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Error in autopilot-start: ${error.message}`);
|
log.error(`Error in autopilot-start: ${error.message}`);
|
||||||
if (error.stack) {
|
if (error.stack) {
|
||||||
context.log.debug(error.stack);
|
log.debug(error.stack);
|
||||||
}
|
}
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
error: { message: `Failed to start workflow: ${error.message}` }
|
error: { message: `Failed to start workflow: ${error.message}` }
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import { handleApiResult, withToolContext } from '../../shared/utils.js';
|
||||||
handleApiResult,
|
import type { ToolContext } from '../../shared/types.js';
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from '../../shared/utils.js';
|
|
||||||
import type { MCPContext } from '../../shared/types.js';
|
|
||||||
import { WorkflowService } from '@tm/core';
|
import { WorkflowService } from '@tm/core';
|
||||||
import type { FastMCP } from 'fastmcp';
|
import type { FastMCP } from 'fastmcp';
|
||||||
|
|
||||||
@@ -29,12 +26,13 @@ export function registerAutopilotStatusTool(server: FastMCP) {
|
|||||||
description:
|
description:
|
||||||
'Get comprehensive workflow status including current phase, progress, subtask details, and activity history.',
|
'Get comprehensive workflow status including current phase, progress, subtask details, and activity history.',
|
||||||
parameters: StatusSchema,
|
parameters: StatusSchema,
|
||||||
execute: withNormalizedProjectRoot(
|
execute: withToolContext(
|
||||||
async (args: StatusArgs, context: MCPContext) => {
|
'autopilot-status',
|
||||||
|
async (args: StatusArgs, { log }: ToolContext) => {
|
||||||
const { projectRoot } = args;
|
const { projectRoot } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.log.info(`Getting workflow status for ${projectRoot}`);
|
log.info(`Getting workflow status for ${projectRoot}`);
|
||||||
|
|
||||||
const workflowService = new WorkflowService(projectRoot);
|
const workflowService = new WorkflowService(projectRoot);
|
||||||
|
|
||||||
@@ -48,7 +46,7 @@ export function registerAutopilotStatusTool(server: FastMCP) {
|
|||||||
'No active workflow found. Start a workflow with autopilot_start'
|
'No active workflow found. Start a workflow with autopilot_start'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -59,22 +57,20 @@ export function registerAutopilotStatusTool(server: FastMCP) {
|
|||||||
// Get status
|
// Get status
|
||||||
const status = workflowService.getStatus();
|
const status = workflowService.getStatus();
|
||||||
|
|
||||||
context.log.info(
|
log.info(`Workflow status retrieved for task ${status.taskId}`);
|
||||||
`Workflow status retrieved for task ${status.taskId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: true,
|
success: true,
|
||||||
data: status
|
data: status
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Error in autopilot-status: ${error.message}`);
|
log.error(`Error in autopilot-status: ${error.message}`);
|
||||||
if (error.stack) {
|
if (error.stack) {
|
||||||
context.log.debug(error.stack);
|
log.debug(error.stack);
|
||||||
}
|
}
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
@@ -83,7 +79,7 @@ export function registerAutopilotStatusTool(server: FastMCP) {
|
|||||||
message: `Failed to get workflow status: ${error.message}`
|
message: `Failed to get workflow status: ${error.message}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withToolContext
|
||||||
} from '../../shared/utils.js';
|
} from '../../shared/utils.js';
|
||||||
import type { MCPContext } from '../../shared/types.js';
|
import type { ToolContext } from '../../shared/types.js';
|
||||||
import { createTmCore, Subtask, type Task } from '@tm/core';
|
import { Subtask, type Task } from '@tm/core';
|
||||||
import type { FastMCP } from 'fastmcp';
|
import type { FastMCP } from 'fastmcp';
|
||||||
|
|
||||||
const GetTaskSchema = z.object({
|
const GetTaskSchema = z.object({
|
||||||
@@ -40,24 +40,16 @@ export function registerGetTaskTool(server: FastMCP) {
|
|||||||
name: 'get_task',
|
name: 'get_task',
|
||||||
description: 'Get detailed information about a specific task',
|
description: 'Get detailed information about a specific task',
|
||||||
parameters: GetTaskSchema,
|
parameters: GetTaskSchema,
|
||||||
execute: withNormalizedProjectRoot(
|
execute: withToolContext(
|
||||||
async (args: GetTaskArgs, context: MCPContext) => {
|
'get-task',
|
||||||
|
async (args: GetTaskArgs, { log, tmCore }: ToolContext) => {
|
||||||
const { id, status, projectRoot, tag } = args;
|
const { id, status, projectRoot, tag } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.log.info(
|
log.info(
|
||||||
`Getting task details for ID: ${id}${status ? ` (filtering subtasks by status: ${status})` : ''} in root: ${projectRoot}`
|
`Getting task details for ID: ${id}${status ? ` (filtering subtasks by status: ${status})` : ''} in root: ${projectRoot}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create tm-core with logging callback
|
|
||||||
const tmCore = await createTmCore({
|
|
||||||
projectPath: projectRoot,
|
|
||||||
loggerConfig: {
|
|
||||||
mcpMode: true,
|
|
||||||
logCallback: context.log
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle comma-separated IDs - parallelize for better performance
|
// Handle comma-separated IDs - parallelize for better performance
|
||||||
const taskIds = id.split(',').map((tid) => tid.trim());
|
const taskIds = id.split(',').map((tid) => tid.trim());
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
@@ -83,7 +75,7 @@ export function registerGetTaskTool(server: FastMCP) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tasks.length === 0) {
|
if (tasks.length === 0) {
|
||||||
context.log.warn(`No tasks found for ID(s): ${id}`);
|
log.warn(`No tasks found for ID(s): ${id}`);
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -91,12 +83,12 @@ export function registerGetTaskTool(server: FastMCP) {
|
|||||||
message: `No tasks found for ID(s): ${id}`
|
message: `No tasks found for ID(s): ${id}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
context.log.info(
|
log.info(
|
||||||
`Successfully retrieved ${tasks.length} task(s) for ID(s): ${id}`
|
`Successfully retrieved ${tasks.length} task(s) for ID(s): ${id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -108,14 +100,14 @@ export function registerGetTaskTool(server: FastMCP) {
|
|||||||
success: true,
|
success: true,
|
||||||
data: responseData
|
data: responseData
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot,
|
projectRoot,
|
||||||
tag
|
tag
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Error in get-task: ${error.message}`);
|
log.error(`Error in get-task: ${error.message}`);
|
||||||
if (error.stack) {
|
if (error.stack) {
|
||||||
context.log.debug(error.stack);
|
log.debug(error.stack);
|
||||||
}
|
}
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
@@ -124,7 +116,7 @@ export function registerGetTaskTool(server: FastMCP) {
|
|||||||
message: `Failed to get task: ${error.message}`
|
message: `Failed to get task: ${error.message}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import {
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withToolContext
|
||||||
} from '../../shared/utils.js';
|
} from '../../shared/utils.js';
|
||||||
import type { MCPContext } from '../../shared/types.js';
|
import type { ToolContext } from '../../shared/types.js';
|
||||||
import { createTmCore, type TaskStatus, type Task } from '@tm/core';
|
import type { TaskStatus, Task } from '@tm/core';
|
||||||
import type { FastMCP } from 'fastmcp';
|
import type { FastMCP } from 'fastmcp';
|
||||||
|
|
||||||
const GetTasksSchema = z.object({
|
const GetTasksSchema = z.object({
|
||||||
@@ -40,24 +40,16 @@ export function registerGetTasksTool(server: FastMCP) {
|
|||||||
description:
|
description:
|
||||||
'Get all tasks from Task Master, optionally filtering by status and including subtasks.',
|
'Get all tasks from Task Master, optionally filtering by status and including subtasks.',
|
||||||
parameters: GetTasksSchema,
|
parameters: GetTasksSchema,
|
||||||
execute: withNormalizedProjectRoot(
|
execute: withToolContext(
|
||||||
async (args: GetTasksArgs, context: MCPContext) => {
|
'get-tasks',
|
||||||
|
async (args: GetTasksArgs, { log, tmCore }: ToolContext) => {
|
||||||
const { projectRoot, status, withSubtasks, tag } = args;
|
const { projectRoot, status, withSubtasks, tag } = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
context.log.info(
|
log.info(
|
||||||
`Getting tasks from ${projectRoot}${status ? ` with status filter: ${status}` : ''}${tag ? ` for tag: ${tag}` : ''}`
|
`Getting tasks from ${projectRoot}${status ? ` with status filter: ${status}` : ''}${tag ? ` for tag: ${tag}` : ''}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create tm-core with logging callback
|
|
||||||
const tmCore = await createTmCore({
|
|
||||||
projectPath: projectRoot,
|
|
||||||
loggerConfig: {
|
|
||||||
mcpMode: true,
|
|
||||||
logCallback: context.log
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build filter
|
// Build filter
|
||||||
const filter =
|
const filter =
|
||||||
status && status !== 'all'
|
status && status !== 'all'
|
||||||
@@ -75,7 +67,7 @@ export function registerGetTasksTool(server: FastMCP) {
|
|||||||
includeSubtasks: withSubtasks
|
includeSubtasks: withSubtasks
|
||||||
});
|
});
|
||||||
|
|
||||||
context.log.info(
|
log.info(
|
||||||
`Retrieved ${result.tasks?.length || 0} tasks (${result.filtered} filtered, ${result.total} total)`
|
`Retrieved ${result.tasks?.length || 0} tasks (${result.filtered} filtered, ${result.total} total)`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -138,14 +130,14 @@ export function registerGetTasksTool(server: FastMCP) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot,
|
projectRoot,
|
||||||
tag: result.tag
|
tag: result.tag
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
context.log.error(`Error in get-tasks: ${error.message}`);
|
log.error(`Error in get-tasks: ${error.message}`);
|
||||||
if (error.stack) {
|
if (error.stack) {
|
||||||
context.log.debug(error.stack);
|
log.debug(error.stack);
|
||||||
}
|
}
|
||||||
return handleApiResult({
|
return handleApiResult({
|
||||||
result: {
|
result: {
|
||||||
@@ -154,7 +146,7 @@ export function registerGetTasksTool(server: FastMCP) {
|
|||||||
message: `Failed to get tasks: ${error.message}`
|
message: `Failed to get tasks: ${error.message}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: context.log,
|
log,
|
||||||
projectRoot
|
projectRoot
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,11 @@
|
|||||||
* Tool for adding a dependency to a task
|
* Tool for adding a dependency to a task
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { createErrorResponse, handleApiResult, withToolContext } from '@tm/mcp';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
handleApiResult,
|
|
||||||
createErrorResponse,
|
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from './utils.js';
|
|
||||||
import { addDependencyDirect } from '../core/task-master-core.js';
|
import { addDependencyDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the addDependency tool with the MCP server
|
* Register the addDependency tool with the MCP server
|
||||||
@@ -37,62 +33,65 @@ export function registerAddDependencyTool(server) {
|
|||||||
.describe('The directory of the project. Must be an absolute path.'),
|
.describe('The directory of the project. Must be an absolute path.'),
|
||||||
tag: z.string().optional().describe('Tag context to operate on')
|
tag: z.string().optional().describe('Tag context to operate on')
|
||||||
}),
|
}),
|
||||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
execute: withToolContext(
|
||||||
try {
|
'add-dependency',
|
||||||
log.info(
|
async (args, { log, session }) => {
|
||||||
`Adding dependency for task ${args.id} to depend on ${args.dependsOn}`
|
|
||||||
);
|
|
||||||
const resolvedTag = resolveTag({
|
|
||||||
projectRoot: args.projectRoot,
|
|
||||||
tag: args.tag
|
|
||||||
});
|
|
||||||
let tasksJsonPath;
|
|
||||||
try {
|
try {
|
||||||
tasksJsonPath = findTasksPath(
|
log.info(
|
||||||
{ projectRoot: args.projectRoot, file: args.file },
|
`Adding dependency for task ${args.id} to depend on ${args.dependsOn}`
|
||||||
log
|
|
||||||
);
|
);
|
||||||
} catch (error) {
|
const resolvedTag = resolveTag({
|
||||||
log.error(`Error finding tasks.json: ${error.message}`);
|
projectRoot: args.projectRoot,
|
||||||
return createErrorResponse(
|
tag: args.tag
|
||||||
`Failed to find tasks.json: ${error.message}`
|
});
|
||||||
);
|
let tasksJsonPath;
|
||||||
}
|
try {
|
||||||
|
tasksJsonPath = findTasksPath(
|
||||||
|
{ projectRoot: args.projectRoot, file: args.file },
|
||||||
|
log
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
log.error(`Error finding tasks.json: ${error.message}`);
|
||||||
|
return createErrorResponse(
|
||||||
|
`Failed to find tasks.json: ${error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Call the direct function with the resolved path
|
// Call the direct function with the resolved path
|
||||||
const result = await addDependencyDirect(
|
const result = await addDependencyDirect(
|
||||||
{
|
{
|
||||||
// Pass the explicitly resolved path
|
// Pass the explicitly resolved path
|
||||||
tasksJsonPath: tasksJsonPath,
|
tasksJsonPath: tasksJsonPath,
|
||||||
// Pass other relevant args
|
// Pass other relevant args
|
||||||
id: args.id,
|
id: args.id,
|
||||||
dependsOn: args.dependsOn,
|
dependsOn: args.dependsOn,
|
||||||
|
projectRoot: args.projectRoot,
|
||||||
|
tag: resolvedTag
|
||||||
|
},
|
||||||
|
log
|
||||||
|
// Remove context object
|
||||||
|
);
|
||||||
|
|
||||||
|
// Log result
|
||||||
|
if (result.success) {
|
||||||
|
log.info(`Successfully added dependency: ${result.data.message}`);
|
||||||
|
} else {
|
||||||
|
log.error(`Failed to add dependency: ${result.error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use handleApiResult to format the response
|
||||||
|
return handleApiResult({
|
||||||
|
result,
|
||||||
|
log,
|
||||||
|
errorPrefix: 'Error adding dependency',
|
||||||
projectRoot: args.projectRoot,
|
projectRoot: args.projectRoot,
|
||||||
tag: resolvedTag
|
tag: resolvedTag
|
||||||
},
|
});
|
||||||
log
|
} catch (error) {
|
||||||
// Remove context object
|
log.error(`Error in addDependency tool: ${error.message}`);
|
||||||
);
|
return createErrorResponse(error.message);
|
||||||
|
|
||||||
// Log result
|
|
||||||
if (result.success) {
|
|
||||||
log.info(`Successfully added dependency: ${result.data.message}`);
|
|
||||||
} else {
|
|
||||||
log.error(`Failed to add dependency: ${result.error.message}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use handleApiResult to format the response
|
|
||||||
return handleApiResult(
|
|
||||||
result,
|
|
||||||
log,
|
|
||||||
'Error adding dependency',
|
|
||||||
undefined,
|
|
||||||
args.projectRoot
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
log.error(`Error in addDependency tool: ${error.message}`);
|
|
||||||
return createErrorResponse(error.message);
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { addSubtaskDirect } from '../core/task-master-core.js';
|
import { addSubtaskDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { addTagDirect } from '../core/task-master-core.js';
|
import { addTagDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { addTaskDirect } from '../core/task-master-core.js';
|
import { addTaskDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { analyzeTaskComplexityDirect } from '../core/task-master-core.js'; // Assuming core functions are exported via task-master-core.js
|
import { analyzeTaskComplexityDirect } from '../core/task-master-core.js'; // Assuming core functions are exported via task-master-core.js
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|||||||
@@ -4,11 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import { handleApiResult, createErrorResponse, withToolContext } from '@tm/mcp';
|
||||||
handleApiResult,
|
|
||||||
createErrorResponse,
|
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from './utils.js';
|
|
||||||
import { clearSubtasksDirect } from '../core/task-master-core.js';
|
import { clearSubtasksDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
@@ -43,9 +39,11 @@ export function registerClearSubtasksTool(server) {
|
|||||||
message: "Either 'id' or 'all' parameter must be provided",
|
message: "Either 'id' or 'all' parameter must be provided",
|
||||||
path: ['id', 'all']
|
path: ['id', 'all']
|
||||||
}),
|
}),
|
||||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
execute: withToolContext('clear-subtasks', async (args, context) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
|
context.log.info(
|
||||||
|
`Clearing subtasks with args: ${JSON.stringify(args)}`
|
||||||
|
);
|
||||||
|
|
||||||
const resolvedTag = resolveTag({
|
const resolvedTag = resolveTag({
|
||||||
projectRoot: args.projectRoot,
|
projectRoot: args.projectRoot,
|
||||||
@@ -57,10 +55,10 @@ export function registerClearSubtasksTool(server) {
|
|||||||
try {
|
try {
|
||||||
tasksJsonPath = findTasksPath(
|
tasksJsonPath = findTasksPath(
|
||||||
{ projectRoot: args.projectRoot, file: args.file },
|
{ projectRoot: args.projectRoot, file: args.file },
|
||||||
log
|
context.log
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error finding tasks.json: ${error.message}`);
|
context.log.error(`Error finding tasks.json: ${error.message}`);
|
||||||
return createErrorResponse(
|
return createErrorResponse(
|
||||||
`Failed to find tasks.json: ${error.message}`
|
`Failed to find tasks.json: ${error.message}`
|
||||||
);
|
);
|
||||||
@@ -75,25 +73,29 @@ export function registerClearSubtasksTool(server) {
|
|||||||
projectRoot: args.projectRoot,
|
projectRoot: args.projectRoot,
|
||||||
tag: resolvedTag
|
tag: resolvedTag
|
||||||
},
|
},
|
||||||
log,
|
context.log,
|
||||||
{ session }
|
{ session: context.session }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`Subtasks cleared successfully: ${result.data.message}`);
|
context.log.info(
|
||||||
|
`Subtasks cleared successfully: ${result.data.message}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to clear subtasks: ${result.error.message}`);
|
context.log.error(
|
||||||
|
`Failed to clear subtasks: ${result.error.message}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleApiResult(
|
return handleApiResult(
|
||||||
result,
|
result,
|
||||||
log,
|
context.log,
|
||||||
'Error clearing subtasks',
|
'Error clearing subtasks',
|
||||||
undefined,
|
undefined,
|
||||||
args.projectRoot
|
args.projectRoot
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in clearSubtasks tool: ${error.message}`);
|
context.log.error(`Error in clearSubtasks tool: ${error.message}`);
|
||||||
return createErrorResponse(error.message);
|
return createErrorResponse(error.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { complexityReportDirect } from '../core/task-master-core.js';
|
import { complexityReportDirect } from '../core/task-master-core.js';
|
||||||
import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js';
|
import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js';
|
||||||
import { findComplexityReportPath } from '../core/utils/path-utils.js';
|
import { findComplexityReportPath } from '../core/utils/path-utils.js';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { copyTagDirect } from '../core/task-master-core.js';
|
import { copyTagDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { deleteTagDirect } from '../core/task-master-core.js';
|
import { deleteTagDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { expandAllTasksDirect } from '../core/task-master-core.js';
|
import { expandAllTasksDirect } from '../core/task-master-core.js';
|
||||||
import {
|
import {
|
||||||
findTasksPath,
|
findTasksPath,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { expandTaskDirect } from '../core/task-master-core.js';
|
import { expandTaskDirect } from '../core/task-master-core.js';
|
||||||
import {
|
import {
|
||||||
findTasksPath,
|
findTasksPath,
|
||||||
|
|||||||
@@ -4,14 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import { handleApiResult, createErrorResponse, withToolContext } from '@tm/mcp';
|
||||||
handleApiResult,
|
|
||||||
createErrorResponse,
|
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from './utils.js';
|
|
||||||
import { fixDependenciesDirect } from '../core/task-master-core.js';
|
import { fixDependenciesDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the fixDependencies tool with the MCP server
|
* Register the fixDependencies tool with the MCP server
|
||||||
* @param {Object} server - FastMCP server instance
|
* @param {Object} server - FastMCP server instance
|
||||||
@@ -27,9 +24,11 @@ export function registerFixDependenciesTool(server) {
|
|||||||
.describe('The directory of the project. Must be an absolute path.'),
|
.describe('The directory of the project. Must be an absolute path.'),
|
||||||
tag: z.string().optional().describe('Tag context to operate on')
|
tag: z.string().optional().describe('Tag context to operate on')
|
||||||
}),
|
}),
|
||||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
execute: withToolContext('fix-dependencies', async (args, context) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Fixing dependencies with args: ${JSON.stringify(args)}`);
|
context.log.info(
|
||||||
|
`Fixing dependencies with args: ${JSON.stringify(args)}`
|
||||||
|
);
|
||||||
|
|
||||||
const resolvedTag = resolveTag({
|
const resolvedTag = resolveTag({
|
||||||
projectRoot: args.projectRoot,
|
projectRoot: args.projectRoot,
|
||||||
@@ -41,10 +40,10 @@ export function registerFixDependenciesTool(server) {
|
|||||||
try {
|
try {
|
||||||
tasksJsonPath = findTasksPath(
|
tasksJsonPath = findTasksPath(
|
||||||
{ projectRoot: args.projectRoot, file: args.file },
|
{ projectRoot: args.projectRoot, file: args.file },
|
||||||
log
|
context.log
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error finding tasks.json: ${error.message}`);
|
context.log.error(`Error finding tasks.json: ${error.message}`);
|
||||||
return createErrorResponse(
|
return createErrorResponse(
|
||||||
`Failed to find tasks.json: ${error.message}`
|
`Failed to find tasks.json: ${error.message}`
|
||||||
);
|
);
|
||||||
@@ -56,24 +55,28 @@ export function registerFixDependenciesTool(server) {
|
|||||||
projectRoot: args.projectRoot,
|
projectRoot: args.projectRoot,
|
||||||
tag: resolvedTag
|
tag: resolvedTag
|
||||||
},
|
},
|
||||||
log
|
context.log
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`Successfully fixed dependencies: ${result.data.message}`);
|
context.log.info(
|
||||||
|
`Successfully fixed dependencies: ${result.data.message}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to fix dependencies: ${result.error.message}`);
|
context.log.error(
|
||||||
|
`Failed to fix dependencies: ${result.error.message}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleApiResult(
|
return handleApiResult(
|
||||||
result,
|
result,
|
||||||
log,
|
context.log,
|
||||||
'Error fixing dependencies',
|
'Error fixing dependencies',
|
||||||
undefined,
|
undefined,
|
||||||
args.projectRoot
|
args.projectRoot
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in fixDependencies tool: ${error.message}`);
|
context.log.error(`Error in fixDependencies tool: ${error.message}`);
|
||||||
return createErrorResponse(error.message);
|
return createErrorResponse(error.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// mcp-server/src/tools/get-operation-status.js
|
// mcp-server/src/tools/get-operation-status.js
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { createErrorResponse, createContentResponse } from './utils.js'; // Assuming these utils exist
|
import { createErrorResponse, createContentResponse } from '@tm/mcp'; // Assuming these utils exist
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the get_operation_status tool.
|
* Register the get_operation_status tool.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { initializeProjectDirect } from '../core/task-master-core.js';
|
import { initializeProjectDirect } from '../core/task-master-core.js';
|
||||||
import { RULE_PROFILES } from '../../../src/constants/profiles.js';
|
import { RULE_PROFILES } from '../../../src/constants/profiles.js';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { listTagsDirect } from '../core/task-master-core.js';
|
import { listTagsDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import { handleApiResult, createErrorResponse, withToolContext } from '@tm/mcp';
|
||||||
handleApiResult,
|
|
||||||
createErrorResponse,
|
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from './utils.js';
|
|
||||||
import { modelsDirect } from '../core/task-master-core.js';
|
import { modelsDirect } from '../core/task-master-core.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,26 +79,28 @@ export function registerModelsTool(server) {
|
|||||||
'Custom base URL for providers that support it (e.g., https://api.example.com/v1).'
|
'Custom base URL for providers that support it (e.g., https://api.example.com/v1).'
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
execute: withToolContext('models', async (args, context) => {
|
||||||
try {
|
try {
|
||||||
log.info(`Starting models tool with args: ${JSON.stringify(args)}`);
|
context.log.info(
|
||||||
|
`Starting models tool with args: ${JSON.stringify(args)}`
|
||||||
|
);
|
||||||
|
|
||||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||||
const result = await modelsDirect(
|
const result = await modelsDirect(
|
||||||
{ ...args, projectRoot: args.projectRoot },
|
{ ...args, projectRoot: args.projectRoot },
|
||||||
log,
|
context.log,
|
||||||
{ session }
|
{ session: context.session }
|
||||||
);
|
);
|
||||||
|
|
||||||
return handleApiResult(
|
return handleApiResult(
|
||||||
result,
|
result,
|
||||||
log,
|
context.log,
|
||||||
'Error managing models',
|
'Error managing models',
|
||||||
undefined,
|
undefined,
|
||||||
args.projectRoot
|
args.projectRoot
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in models tool: ${error.message}`);
|
context.log.error(`Error in models tool: ${error.message}`);
|
||||||
return createErrorResponse(error.message);
|
return createErrorResponse(error.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import {
|
import {
|
||||||
moveTaskDirect,
|
moveTaskDirect,
|
||||||
moveTaskCrossTagDirect
|
moveTaskCrossTagDirect
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { nextTaskDirect } from '../core/task-master-core.js';
|
import { nextTaskDirect } from '../core/task-master-core.js';
|
||||||
import {
|
import {
|
||||||
resolveTasksPath,
|
resolveTasksPath,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
withNormalizedProjectRoot,
|
withNormalizedProjectRoot,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
checkProgressCapability
|
checkProgressCapability
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { parsePRDDirect } from '../core/task-master-core.js';
|
import { parsePRDDirect } from '../core/task-master-core.js';
|
||||||
import {
|
import {
|
||||||
PRD_FILE,
|
PRD_FILE,
|
||||||
|
|||||||
@@ -3,15 +3,11 @@
|
|||||||
* Tool for removing a dependency from a task
|
* Tool for removing a dependency from a task
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { createErrorResponse, handleApiResult, withToolContext } from '@tm/mcp';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
handleApiResult,
|
|
||||||
createErrorResponse,
|
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from './utils.js';
|
|
||||||
import { removeDependencyDirect } from '../core/task-master-core.js';
|
import { removeDependencyDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the removeDependency tool with the MCP server
|
* Register the removeDependency tool with the MCP server
|
||||||
@@ -35,25 +31,25 @@ export function registerRemoveDependencyTool(server) {
|
|||||||
.describe('The directory of the project. Must be an absolute path.'),
|
.describe('The directory of the project. Must be an absolute path.'),
|
||||||
tag: z.string().optional().describe('Tag context to operate on')
|
tag: z.string().optional().describe('Tag context to operate on')
|
||||||
}),
|
}),
|
||||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
execute: withToolContext('remove-dependency', async (args, context) => {
|
||||||
try {
|
try {
|
||||||
const resolvedTag = resolveTag({
|
const resolvedTag = resolveTag({
|
||||||
projectRoot: args.projectRoot,
|
projectRoot: args.projectRoot,
|
||||||
tag: args.tag
|
tag: args.tag
|
||||||
});
|
});
|
||||||
log.info(
|
context.log.info(
|
||||||
`Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}`
|
`Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
// Use args.projectRoot directly (guaranteed by withToolContext)
|
||||||
let tasksJsonPath;
|
let tasksJsonPath;
|
||||||
try {
|
try {
|
||||||
tasksJsonPath = findTasksPath(
|
tasksJsonPath = findTasksPath(
|
||||||
{ projectRoot: args.projectRoot, file: args.file },
|
{ projectRoot: args.projectRoot, file: args.file },
|
||||||
log
|
context.log
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error finding tasks.json: ${error.message}`);
|
context.log.error(`Error finding tasks.json: ${error.message}`);
|
||||||
return createErrorResponse(
|
return createErrorResponse(
|
||||||
`Failed to find tasks.json: ${error.message}`
|
`Failed to find tasks.json: ${error.message}`
|
||||||
);
|
);
|
||||||
@@ -67,24 +63,28 @@ export function registerRemoveDependencyTool(server) {
|
|||||||
projectRoot: args.projectRoot,
|
projectRoot: args.projectRoot,
|
||||||
tag: resolvedTag
|
tag: resolvedTag
|
||||||
},
|
},
|
||||||
log
|
context.log
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
log.info(`Successfully removed dependency: ${result.data.message}`);
|
context.log.info(
|
||||||
|
`Successfully removed dependency: ${result.data.message}`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
log.error(`Failed to remove dependency: ${result.error.message}`);
|
context.log.error(
|
||||||
|
`Failed to remove dependency: ${result.error.message}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleApiResult(
|
return handleApiResult(
|
||||||
result,
|
result,
|
||||||
log,
|
context.log,
|
||||||
'Error removing dependency',
|
'Error removing dependency',
|
||||||
undefined,
|
undefined,
|
||||||
args.projectRoot
|
args.projectRoot
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in removeDependency tool: ${error.message}`);
|
context.log.error(`Error in removeDependency tool: ${error.message}`);
|
||||||
return createErrorResponse(error.message);
|
return createErrorResponse(error.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { removeSubtaskDirect } from '../core/task-master-core.js';
|
import { removeSubtaskDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { removeTaskDirect } from '../core/task-master-core.js';
|
import { removeTaskDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { renameTagDirect } from '../core/task-master-core.js';
|
import { renameTagDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { researchDirect } from '../core/task-master-core.js';
|
import { researchDirect } from '../core/task-master-core.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { responseLanguageDirect } from '../core/direct-functions/response-language.js';
|
import { responseLanguageDirect } from '../core/direct-functions/response-language.js';
|
||||||
|
|
||||||
export function registerResponseLanguageTool(server) {
|
export function registerResponseLanguageTool(server) {
|
||||||
@@ -36,7 +36,12 @@ export function registerResponseLanguageTool(server) {
|
|||||||
log,
|
log,
|
||||||
{ session }
|
{ session }
|
||||||
);
|
);
|
||||||
return handleApiResult(result, log, 'Error setting response language');
|
return handleApiResult({
|
||||||
|
result,
|
||||||
|
log,
|
||||||
|
errorPrefix: 'Error setting response language',
|
||||||
|
projectRoot: args.projectRoot
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in response-language tool: ${error.message}`);
|
log.error(`Error in response-language tool: ${error.message}`);
|
||||||
return createErrorResponse(error.message);
|
return createErrorResponse(error.message);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { rulesDirect } from '../core/direct-functions/rules.js';
|
import { rulesDirect } from '../core/direct-functions/rules.js';
|
||||||
import { RULE_PROFILES } from '../../../src/constants/profiles.js';
|
import { RULE_PROFILES } from '../../../src/constants/profiles.js';
|
||||||
|
|
||||||
@@ -49,7 +49,11 @@ export function registerRulesTool(server) {
|
|||||||
`[rules tool] Executing action: ${args.action} for profiles: ${args.profiles.join(', ')} in ${args.projectRoot}`
|
`[rules tool] Executing action: ${args.action} for profiles: ${args.profiles.join(', ')} in ${args.projectRoot}`
|
||||||
);
|
);
|
||||||
const result = await rulesDirect(args, log, { session });
|
const result = await rulesDirect(args, log, { session });
|
||||||
return handleApiResult(result, log);
|
return handleApiResult({
|
||||||
|
result,
|
||||||
|
log,
|
||||||
|
projectRoot: args.projectRoot
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`[rules tool] Error: ${error.message}`);
|
log.error(`[rules tool] Error: ${error.message}`);
|
||||||
return createErrorResponse(error.message, { details: error.stack });
|
return createErrorResponse(error.message, { details: error.stack });
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { scopeDownDirect } from '../core/task-master-core.js';
|
import { scopeDownDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { scopeUpDirect } from '../core/task-master-core.js';
|
import { scopeUpDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import {
|
import {
|
||||||
setTaskStatusDirect,
|
setTaskStatusDirect,
|
||||||
nextTaskDirect
|
nextTaskDirect
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { updateSubtaskByIdDirect } from '../core/task-master-core.js';
|
import { updateSubtaskByIdDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { updateTaskByIdDirect } from '../core/task-master-core.js';
|
import { updateTaskByIdDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
handleApiResult,
|
handleApiResult,
|
||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { updateTasksDirect } from '../core/task-master-core.js';
|
import { updateTasksDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
createErrorResponse,
|
createErrorResponse,
|
||||||
handleApiResult,
|
handleApiResult,
|
||||||
withNormalizedProjectRoot
|
withNormalizedProjectRoot
|
||||||
} from './utils.js';
|
} from '@tm/mcp';
|
||||||
import { useTagDirect } from '../core/task-master-core.js';
|
import { useTagDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,11 @@
|
|||||||
* Tool for validating task dependencies
|
* Tool for validating task dependencies
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { createErrorResponse, handleApiResult, withToolContext } from '@tm/mcp';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||||
handleApiResult,
|
|
||||||
createErrorResponse,
|
|
||||||
withNormalizedProjectRoot
|
|
||||||
} from './utils.js';
|
|
||||||
import { validateDependenciesDirect } from '../core/task-master-core.js';
|
import { validateDependenciesDirect } from '../core/task-master-core.js';
|
||||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the validateDependencies tool with the MCP server
|
* Register the validateDependencies tool with the MCP server
|
||||||
@@ -29,56 +25,63 @@ export function registerValidateDependenciesTool(server) {
|
|||||||
.describe('The directory of the project. Must be an absolute path.'),
|
.describe('The directory of the project. Must be an absolute path.'),
|
||||||
tag: z.string().optional().describe('Tag context to operate on')
|
tag: z.string().optional().describe('Tag context to operate on')
|
||||||
}),
|
}),
|
||||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
execute: withToolContext(
|
||||||
try {
|
'validate-dependencies',
|
||||||
const resolvedTag = resolveTag({
|
async (args, { log, session }) => {
|
||||||
projectRoot: args.projectRoot,
|
|
||||||
tag: args.tag
|
|
||||||
});
|
|
||||||
log.info(`Validating dependencies with args: ${JSON.stringify(args)}`);
|
|
||||||
|
|
||||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
|
||||||
let tasksJsonPath;
|
|
||||||
try {
|
try {
|
||||||
tasksJsonPath = findTasksPath(
|
const resolvedTag = resolveTag({
|
||||||
{ projectRoot: args.projectRoot, file: args.file },
|
projectRoot: args.projectRoot,
|
||||||
|
tag: args.tag
|
||||||
|
});
|
||||||
|
log.info(
|
||||||
|
`Validating dependencies with args: ${JSON.stringify(args)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use args.projectRoot directly (guaranteed by withToolContext)
|
||||||
|
let tasksJsonPath;
|
||||||
|
try {
|
||||||
|
tasksJsonPath = findTasksPath(
|
||||||
|
{ projectRoot: args.projectRoot, file: args.file },
|
||||||
|
log
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
log.error(`Error finding tasks.json: ${error.message}`);
|
||||||
|
return createErrorResponse(
|
||||||
|
`Failed to find tasks.json: ${error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await validateDependenciesDirect(
|
||||||
|
{
|
||||||
|
tasksJsonPath: tasksJsonPath,
|
||||||
|
projectRoot: args.projectRoot,
|
||||||
|
tag: resolvedTag
|
||||||
|
},
|
||||||
log
|
log
|
||||||
);
|
);
|
||||||
} catch (error) {
|
|
||||||
log.error(`Error finding tasks.json: ${error.message}`);
|
|
||||||
return createErrorResponse(
|
|
||||||
`Failed to find tasks.json: ${error.message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await validateDependenciesDirect(
|
if (result.success) {
|
||||||
{
|
log.info(
|
||||||
tasksJsonPath: tasksJsonPath,
|
`Successfully validated dependencies: ${result.data.message}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log.error(
|
||||||
|
`Failed to validate dependencies: ${result.error.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleApiResult({
|
||||||
|
result,
|
||||||
|
log,
|
||||||
|
errorPrefix: 'Error validating dependencies',
|
||||||
projectRoot: args.projectRoot,
|
projectRoot: args.projectRoot,
|
||||||
tag: resolvedTag
|
tag: resolvedTag
|
||||||
},
|
});
|
||||||
log
|
} catch (error) {
|
||||||
);
|
log.error(`Error in validateDependencies tool: ${error.message}`);
|
||||||
|
return createErrorResponse(error.message);
|
||||||
if (result.success) {
|
|
||||||
log.info(
|
|
||||||
`Successfully validated dependencies: ${result.data.message}`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log.error(`Failed to validate dependencies: ${result.error.message}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleApiResult(
|
|
||||||
result,
|
|
||||||
log,
|
|
||||||
'Error validating dependencies',
|
|
||||||
undefined,
|
|
||||||
args.projectRoot
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
log.error(`Error in validateDependencies tool: ${error.message}`);
|
|
||||||
return createErrorResponse(error.message);
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
1707
package-lock.json
generated
1707
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -82,6 +82,12 @@ export type {
|
|||||||
} from './modules/auth/types.js';
|
} from './modules/auth/types.js';
|
||||||
export { AuthenticationError } from './modules/auth/types.js';
|
export { AuthenticationError } from './modules/auth/types.js';
|
||||||
|
|
||||||
|
// Auth constants
|
||||||
|
export {
|
||||||
|
LOCAL_ONLY_COMMANDS,
|
||||||
|
type LocalOnlyCommand
|
||||||
|
} from './modules/auth/index.js';
|
||||||
|
|
||||||
// Brief types
|
// Brief types
|
||||||
export type { Brief } from './modules/briefs/types.js';
|
export type { Brief } from './modules/briefs/types.js';
|
||||||
export type { TagWithStats } from './modules/briefs/services/brief-service.js';
|
export type { TagWithStats } from './modules/briefs/services/brief-service.js';
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import type {
|
|||||||
OAuthFlowOptions,
|
OAuthFlowOptions,
|
||||||
UserContext
|
UserContext
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
import { checkAuthBlock, type AuthBlockResult } from './command.guard.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display information for storage context
|
* Display information for storage context
|
||||||
@@ -225,6 +226,41 @@ export class AuthDomain {
|
|||||||
return `${baseUrl}/home/${context.orgSlug}/briefs/create`;
|
return `${baseUrl}/home/${context.orgSlug}/briefs/create`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== Command Guards ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a local-only command should be blocked when using API storage
|
||||||
|
*
|
||||||
|
* Local-only commands (like dependency management) are blocked when authenticated
|
||||||
|
* with Hamster and using API storage, since Hamster manages these features remotely.
|
||||||
|
*
|
||||||
|
* @param commandName - Name of the command to check
|
||||||
|
* @param storageType - Current storage type being used
|
||||||
|
* @returns Guard result with blocking decision and context
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const result = await tmCore.auth.guardCommand('add-dependency', tmCore.tasks.getStorageType());
|
||||||
|
* if (result.isBlocked) {
|
||||||
|
* console.log(`Command blocked: ${result.briefName}`);
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
async guardCommand(
|
||||||
|
commandName: string,
|
||||||
|
storageType: StorageType
|
||||||
|
): Promise<AuthBlockResult> {
|
||||||
|
const hasValidSession = await this.hasValidSession();
|
||||||
|
const context = this.getContext();
|
||||||
|
|
||||||
|
return checkAuthBlock({
|
||||||
|
hasValidSession,
|
||||||
|
briefName: context?.briefName,
|
||||||
|
storageType,
|
||||||
|
commandName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get web app base URL from environment configuration
|
* Get web app base URL from environment configuration
|
||||||
* @private
|
* @private
|
||||||
|
|||||||
77
packages/tm-core/src/modules/auth/command.guard.ts
Normal file
77
packages/tm-core/src/modules/auth/command.guard.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Command guard - Core logic for blocking local-only commands
|
||||||
|
* Pure business logic - no presentation layer concerns
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { StorageType } from '../../common/types/index.js';
|
||||||
|
import { LOCAL_ONLY_COMMANDS, type LocalOnlyCommand } from './constants.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result from checking if a command should be blocked
|
||||||
|
*/
|
||||||
|
export interface AuthBlockResult {
|
||||||
|
/** Whether the command should be blocked */
|
||||||
|
isBlocked: boolean;
|
||||||
|
/** Brief name if authenticated with Hamster */
|
||||||
|
briefName?: string;
|
||||||
|
/** Command name that was checked */
|
||||||
|
commandName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a command is local-only
|
||||||
|
*/
|
||||||
|
export function isLocalOnlyCommand(
|
||||||
|
commandName: string
|
||||||
|
): commandName is LocalOnlyCommand {
|
||||||
|
return LOCAL_ONLY_COMMANDS.includes(commandName as LocalOnlyCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters for auth block check
|
||||||
|
*/
|
||||||
|
export interface AuthBlockParams {
|
||||||
|
/** Whether user has a valid auth session */
|
||||||
|
hasValidSession: boolean;
|
||||||
|
/** Brief name from auth context */
|
||||||
|
briefName?: string;
|
||||||
|
/** Current storage type being used */
|
||||||
|
storageType: StorageType;
|
||||||
|
/** Command name to check */
|
||||||
|
commandName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a command should be blocked because user is authenticated with Hamster
|
||||||
|
*
|
||||||
|
* This is pure business logic with dependency injection - returns data only, no display/formatting
|
||||||
|
* Presentation layers (CLI, MCP) should format the response appropriately
|
||||||
|
*
|
||||||
|
* @param params - Auth block parameters
|
||||||
|
* @returns AuthBlockResult with blocking decision and context
|
||||||
|
*/
|
||||||
|
export function checkAuthBlock(params: AuthBlockParams): AuthBlockResult {
|
||||||
|
const { hasValidSession, briefName, storageType, commandName } = params;
|
||||||
|
|
||||||
|
// Only check auth for local-only commands
|
||||||
|
if (!isLocalOnlyCommand(commandName)) {
|
||||||
|
return { isBlocked: false, commandName };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not authenticated - command is allowed
|
||||||
|
if (!hasValidSession) {
|
||||||
|
return { isBlocked: false, commandName };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticated but using file storage - command is allowed
|
||||||
|
if (storageType !== 'api') {
|
||||||
|
return { isBlocked: false, commandName };
|
||||||
|
}
|
||||||
|
|
||||||
|
// User is authenticated AND using API storage - block the command
|
||||||
|
return {
|
||||||
|
isBlocked: true,
|
||||||
|
briefName: briefName || 'remote brief',
|
||||||
|
commandName
|
||||||
|
};
|
||||||
|
}
|
||||||
18
packages/tm-core/src/modules/auth/constants.ts
Normal file
18
packages/tm-core/src/modules/auth/constants.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Auth module constants
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commands that are only available for local file storage
|
||||||
|
* These commands are blocked when using Hamster (API storage)
|
||||||
|
*/
|
||||||
|
export const LOCAL_ONLY_COMMANDS = [
|
||||||
|
'add-dependency',
|
||||||
|
'remove-dependency',
|
||||||
|
'validate-dependencies',
|
||||||
|
'fix-dependencies',
|
||||||
|
'clear-subtasks',
|
||||||
|
'models'
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type LocalOnlyCommand = (typeof LOCAL_ONLY_COMMANDS)[number];
|
||||||
@@ -26,3 +26,9 @@ export {
|
|||||||
DEFAULT_AUTH_CONFIG,
|
DEFAULT_AUTH_CONFIG,
|
||||||
getAuthConfig
|
getAuthConfig
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
|
|
||||||
|
// Command guard types and utilities
|
||||||
|
export { isLocalOnlyCommand, type AuthBlockResult } from './command.guard.js';
|
||||||
|
|
||||||
|
// Auth constants
|
||||||
|
export { LOCAL_ONLY_COMMANDS, type LocalOnlyCommand } from './constants.js';
|
||||||
|
|||||||
@@ -3,139 +3,142 @@
|
|||||||
* Command-line interface for the Task Master CLI
|
* Command-line interface for the Task Master CLI
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Command } from 'commander';
|
|
||||||
import path from 'path';
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import boxen from 'boxen';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import boxen from 'boxen';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { Command } from 'commander';
|
||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
|
|
||||||
import { log, readJSON } from './utils.js';
|
|
||||||
// Import command registry and utilities from @tm/cli
|
// Import command registry and utilities from @tm/cli
|
||||||
import {
|
import {
|
||||||
registerAllCommands,
|
|
||||||
checkForUpdate,
|
checkForUpdate,
|
||||||
performAutoUpdate,
|
|
||||||
displayUpgradeNotification,
|
|
||||||
restartWithNewVersion,
|
|
||||||
displayError,
|
displayError,
|
||||||
|
displayUpgradeNotification,
|
||||||
|
performAutoUpdate,
|
||||||
|
registerAllCommands,
|
||||||
|
restartWithNewVersion,
|
||||||
runInteractiveSetup
|
runInteractiveSetup
|
||||||
} from '@tm/cli';
|
} from '@tm/cli';
|
||||||
|
import { log, readJSON } from './utils.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
parsePRD,
|
|
||||||
updateTasks,
|
|
||||||
expandTask,
|
|
||||||
expandAllTasks,
|
|
||||||
clearSubtasks,
|
|
||||||
addTask,
|
|
||||||
addSubtask,
|
addSubtask,
|
||||||
removeSubtask,
|
addTask,
|
||||||
analyzeTaskComplexity,
|
analyzeTaskComplexity,
|
||||||
updateTaskById,
|
clearSubtasks,
|
||||||
updateSubtaskById,
|
expandAllTasks,
|
||||||
removeTask,
|
expandTask,
|
||||||
findTaskById,
|
findTaskById,
|
||||||
taskExists,
|
|
||||||
moveTask,
|
|
||||||
migrateProject,
|
migrateProject,
|
||||||
setResponseLanguage,
|
moveTask,
|
||||||
scopeUpTask,
|
parsePRD,
|
||||||
|
removeSubtask,
|
||||||
|
removeTask,
|
||||||
scopeDownTask,
|
scopeDownTask,
|
||||||
|
scopeUpTask,
|
||||||
|
setResponseLanguage,
|
||||||
|
taskExists,
|
||||||
|
updateSubtaskById,
|
||||||
|
updateTaskById,
|
||||||
|
updateTasks,
|
||||||
validateStrength
|
validateStrength
|
||||||
} from './task-manager.js';
|
} from './task-manager.js';
|
||||||
|
|
||||||
import { moveTasksBetweenTags } from './task-manager/move-task.js';
|
import { moveTasksBetweenTags } from './task-manager/move-task.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
copyTag,
|
||||||
createTag,
|
createTag,
|
||||||
deleteTag,
|
deleteTag,
|
||||||
tags,
|
|
||||||
useTag,
|
|
||||||
renameTag,
|
renameTag,
|
||||||
copyTag
|
tags,
|
||||||
|
useTag
|
||||||
} from './task-manager/tag-management.js';
|
} from './task-manager/tag-management.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addDependency,
|
addDependency,
|
||||||
|
fixDependenciesCommand,
|
||||||
removeDependency,
|
removeDependency,
|
||||||
validateDependenciesCommand,
|
validateDependenciesCommand
|
||||||
fixDependenciesCommand
|
|
||||||
} from './dependency-manager.js';
|
} from './dependency-manager.js';
|
||||||
|
|
||||||
|
import { checkAndBlockIfAuthenticated } from '@tm/cli';
|
||||||
|
import { LOCAL_ONLY_COMMANDS } from '@tm/core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isApiKeySet,
|
|
||||||
getDebugFlag,
|
|
||||||
ConfigurationError,
|
ConfigurationError,
|
||||||
isConfigFilePresent,
|
getDebugFlag,
|
||||||
getDefaultNumTasks
|
getDefaultNumTasks,
|
||||||
|
isApiKeySet,
|
||||||
|
isConfigFilePresent
|
||||||
} from './config-manager.js';
|
} from './config-manager.js';
|
||||||
|
|
||||||
import { CUSTOM_PROVIDERS } from '@tm/core';
|
import { CUSTOM_PROVIDERS } from '@tm/core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
COMPLEXITY_REPORT_FILE,
|
COMPLEXITY_REPORT_FILE,
|
||||||
TASKMASTER_TASKS_FILE,
|
TASKMASTER_DOCS_DIR,
|
||||||
TASKMASTER_DOCS_DIR
|
TASKMASTER_TASKS_FILE
|
||||||
} from '../../src/constants/paths.js';
|
} from '../../src/constants/paths.js';
|
||||||
|
|
||||||
import { initTaskMaster } from '../../src/task-master.js';
|
import { initTaskMaster } from '../../src/task-master.js';
|
||||||
|
|
||||||
import {
|
|
||||||
displayBanner,
|
|
||||||
displayHelp,
|
|
||||||
displayComplexityReport,
|
|
||||||
getStatusWithColor,
|
|
||||||
confirmTaskOverwrite,
|
|
||||||
startLoadingIndicator,
|
|
||||||
stopLoadingIndicator,
|
|
||||||
displayModelConfiguration,
|
|
||||||
displayAvailableModels,
|
|
||||||
displayApiKeyStatus,
|
|
||||||
displayTaggedTasksFYI,
|
|
||||||
displayCurrentTagIndicator,
|
|
||||||
displayCrossTagDependencyError,
|
|
||||||
displaySubtaskMoveError,
|
|
||||||
displayInvalidTagCombinationError,
|
|
||||||
displayDependencyValidationHints
|
|
||||||
} from './ui.js';
|
|
||||||
import {
|
import {
|
||||||
confirmProfilesRemove,
|
confirmProfilesRemove,
|
||||||
confirmRemoveAllRemainingProfiles
|
confirmRemoveAllRemainingProfiles
|
||||||
} from '../../src/ui/confirm.js';
|
} from '../../src/ui/confirm.js';
|
||||||
import {
|
import {
|
||||||
wouldRemovalLeaveNoProfiles,
|
getInstalledProfiles,
|
||||||
getInstalledProfiles
|
wouldRemovalLeaveNoProfiles
|
||||||
} from '../../src/utils/profiles.js';
|
} from '../../src/utils/profiles.js';
|
||||||
|
import {
|
||||||
|
confirmTaskOverwrite,
|
||||||
|
displayApiKeyStatus,
|
||||||
|
displayAvailableModels,
|
||||||
|
displayBanner,
|
||||||
|
displayComplexityReport,
|
||||||
|
displayCrossTagDependencyError,
|
||||||
|
displayCurrentTagIndicator,
|
||||||
|
displayDependencyValidationHints,
|
||||||
|
displayHelp,
|
||||||
|
displayInvalidTagCombinationError,
|
||||||
|
displayModelConfiguration,
|
||||||
|
displaySubtaskMoveError,
|
||||||
|
displayTaggedTasksFYI,
|
||||||
|
getStatusWithColor,
|
||||||
|
startLoadingIndicator,
|
||||||
|
stopLoadingIndicator
|
||||||
|
} from './ui.js';
|
||||||
|
|
||||||
import { initializeProject } from '../init.js';
|
|
||||||
import {
|
|
||||||
getModelConfiguration,
|
|
||||||
getAvailableModelsList,
|
|
||||||
setModel,
|
|
||||||
getApiKeyStatusReport
|
|
||||||
} from './task-manager/models.js';
|
|
||||||
import {
|
|
||||||
isValidRulesAction,
|
|
||||||
RULES_ACTIONS,
|
|
||||||
RULES_SETUP_ACTION
|
|
||||||
} from '../../src/constants/rules-actions.js';
|
|
||||||
import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
|
|
||||||
import { syncTasksToReadme } from './sync-readme.js';
|
|
||||||
import { RULE_PROFILES } from '../../src/constants/profiles.js';
|
import { RULE_PROFILES } from '../../src/constants/profiles.js';
|
||||||
import {
|
import {
|
||||||
convertAllRulesToProfileRules,
|
RULES_ACTIONS,
|
||||||
removeProfileRules,
|
RULES_SETUP_ACTION,
|
||||||
isValidProfile,
|
isValidRulesAction
|
||||||
getRulesProfile
|
} from '../../src/constants/rules-actions.js';
|
||||||
} from '../../src/utils/rule-transformer.js';
|
import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
|
||||||
import {
|
import {
|
||||||
runInteractiveProfilesSetup,
|
|
||||||
generateProfileSummary,
|
|
||||||
categorizeProfileResults,
|
categorizeProfileResults,
|
||||||
|
categorizeRemovalResults,
|
||||||
generateProfileRemovalSummary,
|
generateProfileRemovalSummary,
|
||||||
categorizeRemovalResults
|
generateProfileSummary,
|
||||||
|
runInteractiveProfilesSetup
|
||||||
} from '../../src/utils/profiles.js';
|
} from '../../src/utils/profiles.js';
|
||||||
|
import {
|
||||||
|
convertAllRulesToProfileRules,
|
||||||
|
getRulesProfile,
|
||||||
|
isValidProfile,
|
||||||
|
removeProfileRules
|
||||||
|
} from '../../src/utils/rule-transformer.js';
|
||||||
|
import { initializeProject } from '../init.js';
|
||||||
|
import { syncTasksToReadme } from './sync-readme.js';
|
||||||
|
import {
|
||||||
|
getApiKeyStatusReport,
|
||||||
|
getAvailableModelsList,
|
||||||
|
getModelConfiguration,
|
||||||
|
setModel
|
||||||
|
} from './task-manager/models.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure and register CLI commands
|
* Configure and register CLI commands
|
||||||
@@ -154,6 +157,23 @@ function registerCommands(programInstance) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add global command guard for local-only commands
|
||||||
|
programInstance.hook('preAction', async (thisCommand, actionCommand) => {
|
||||||
|
const commandName = actionCommand.name();
|
||||||
|
|
||||||
|
// Only check if it's a local-only command
|
||||||
|
if (LOCAL_ONLY_COMMANDS.includes(commandName)) {
|
||||||
|
const taskMaster = initTaskMaster(actionCommand.opts());
|
||||||
|
const isBlocked = await checkAndBlockIfAuthenticated(
|
||||||
|
commandName,
|
||||||
|
taskMaster.getProjectRoot()
|
||||||
|
);
|
||||||
|
if (isBlocked) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// parse-prd command
|
// parse-prd command
|
||||||
programInstance
|
programInstance
|
||||||
.command('parse-prd')
|
.command('parse-prd')
|
||||||
|
|||||||
Reference in New Issue
Block a user