directus-task-management/planning/06-implementation-guides.md

1450 lines
39 KiB
Markdown

# Directus Task Management Suite - Implementation Guides
## Implementation Overview
This document provides detailed technical implementation guides for building the Directus Task Management Suite, organized by development phase with specific code examples, configuration steps, and best practices.
## Phase 1: Foundation Implementation Guide
### 1.1 Directus Collections Setup
#### Core Collections Schema Implementation
**Step 1: Create Projects Collection**
```sql
-- Execute via Directus API or admin interface
{
"collection": "projects",
"schema": {
"id": {
"type": "uuid",
"default": "$GENERATE_UUID",
"primary_key": true
},
"name": {
"type": "string",
"required": true,
"max_length": 255
},
"description": {
"type": "text",
"nullable": true
},
"status": {
"type": "string",
"default": "active",
"options": {
"choices": [
{"text": "Active", "value": "active"},
{"text": "On Hold", "value": "on_hold"},
{"text": "Completed", "value": "completed"},
{"text": "Archived", "value": "archived"}
]
}
},
"parent_project": {
"type": "uuid",
"foreign_key": "projects.id",
"nullable": true
},
"repository_url": {
"type": "string",
"nullable": true
},
"bmad_workflow_type": {
"type": "string",
"options": {
"choices": [
{"text": "Greenfield", "value": "greenfield"},
{"text": "Brownfield", "value": "brownfield"},
{"text": "Maintenance", "value": "maintenance"}
]
}
},
"task_master_project_id": {
"type": "string",
"nullable": true
},
"priority": {
"type": "string",
"default": "medium",
"options": {
"choices": [
{"text": "Low", "value": "low"},
{"text": "Medium", "value": "medium"},
{"text": "High", "value": "high"},
{"text": "Critical", "value": "critical"}
]
}
},
"start_date": {
"type": "date",
"nullable": true
},
"due_date": {
"type": "date",
"nullable": true
},
"completion_percentage": {
"type": "integer",
"default": 0,
"validation": {
"min": 0,
"max": 100
}
},
"created_by": {
"type": "uuid",
"foreign_key": "directus_users.id"
},
"assigned_to": {
"type": "uuid",
"foreign_key": "directus_users.id",
"nullable": true
},
"metadata": {
"type": "json",
"nullable": true
},
"created_at": {
"type": "timestamp",
"auto_create": true
},
"updated_at": {
"type": "timestamp",
"auto_update": true
}
}
}
```
**Step 2: Create Task Statuses Collection**
```sql
{
"collection": "task_statuses",
"schema": {
"id": {
"type": "uuid",
"default": "$GENERATE_UUID",
"primary_key": true
},
"name": {
"type": "string",
"required": true,
"unique": true
},
"slug": {
"type": "string",
"required": true,
"unique": true
},
"description": {
"type": "text",
"nullable": true
},
"color": {
"type": "string",
"default": "#6366f1",
"validation": {
"pattern": "^#[0-9A-Fa-f]{6}$"
}
},
"icon": {
"type": "string",
"default": "circle"
},
"category": {
"type": "string",
"required": true,
"options": {
"choices": [
{"text": "To Do", "value": "todo"},
{"text": "In Progress", "value": "in_progress"},
{"text": "Review", "value": "review"},
{"text": "Done", "value": "done"},
{"text": "Blocked", "value": "blocked"}
]
}
},
"is_final": {
"type": "boolean",
"default": false
},
"auto_transition_rules": {
"type": "json",
"nullable": true
},
"workflow_type": {
"type": "string",
"options": {
"choices": [
{"text": "BMad", "value": "bmad"},
{"text": "Task Master", "value": "task_master"},
{"text": "GitHub", "value": "github"},
{"text": "Custom", "value": "custom"}
]
}
},
"sort_order": {
"type": "integer",
"default": 0
},
"active": {
"type": "boolean",
"default": true
},
"created_at": {
"type": "timestamp",
"auto_create": true
},
"updated_at": {
"type": "timestamp",
"auto_update": true
}
}
}
```
**Step 3: Create Tasks Collection**
```sql
{
"collection": "tasks",
"schema": {
"id": {
"type": "uuid",
"default": "$GENERATE_UUID",
"primary_key": true
},
"title": {
"type": "string",
"required": true,
"max_length": 255
},
"description": {
"type": "text",
"nullable": true
},
"status": {
"type": "uuid",
"foreign_key": "task_statuses.id",
"required": true
},
"priority": {
"type": "string",
"default": "medium",
"options": {
"choices": [
{"text": "Lowest", "value": "lowest"},
{"text": "Low", "value": "low"},
{"text": "Medium", "value": "medium"},
{"text": "High", "value": "high"},
{"text": "Highest", "value": "highest"}
]
}
},
"task_type": {
"type": "string",
"default": "feature",
"options": {
"choices": [
{"text": "Feature", "value": "feature"},
{"text": "Bug", "value": "bug"},
{"text": "Enhancement", "value": "enhancement"},
{"text": "Research", "value": "research"},
{"text": "Maintenance", "value": "maintenance"}
]
}
},
"complexity": {
"type": "string",
"options": {
"choices": [
{"text": "Trivial", "value": "trivial"},
{"text": "Minor", "value": "minor"},
{"text": "Major", "value": "major"},
{"text": "Critical", "value": "critical"}
]
}
},
"story_points": {
"type": "integer",
"nullable": true,
"validation": {
"min": 1,
"max": 21
}
},
"project": {
"type": "uuid",
"foreign_key": "projects.id",
"required": true
},
"parent_task": {
"type": "uuid",
"foreign_key": "tasks.id",
"nullable": true
},
"epic": {
"type": "uuid",
"foreign_key": "tasks.id",
"nullable": true
},
"created_by": {
"type": "uuid",
"foreign_key": "directus_users.id"
},
"assigned_to": {
"type": "uuid",
"foreign_key": "directus_users.id",
"nullable": true
},
"ai_agent_assigned": {
"type": "string",
"nullable": true
},
"reviewer": {
"type": "uuid",
"foreign_key": "directus_users.id",
"nullable": true
},
"estimated_hours": {
"type": "decimal",
"precision": 5,
"scale": 2,
"nullable": true
},
"actual_hours": {
"type": "decimal",
"precision": 5,
"scale": 2,
"computed": "SUM(task_time_entries.hours)"
},
"progress_percentage": {
"type": "integer",
"default": 0,
"validation": {
"min": 0,
"max": 100
}
},
"start_date": {
"type": "date",
"nullable": true
},
"due_date": {
"type": "date",
"nullable": true
},
"completed_at": {
"type": "timestamp",
"nullable": true
},
"task_master_id": {
"type": "string",
"nullable": true,
"unique": true
},
"github_issue_url": {
"type": "string",
"nullable": true
},
"bmad_story_id": {
"type": "string",
"nullable": true
},
"external_refs": {
"type": "json",
"nullable": true
},
"ai_generated": {
"type": "boolean",
"default": false
},
"ai_context": {
"type": "json",
"nullable": true
},
"auto_status_updates": {
"type": "boolean",
"default": false
},
"acceptance_criteria": {
"type": "text",
"nullable": true
},
"definition_of_done": {
"type": "text",
"nullable": true
},
"notes": {
"type": "text",
"nullable": true
},
"created_at": {
"type": "timestamp",
"auto_create": true
},
"updated_at": {
"type": "timestamp",
"auto_update": true
}
}
}
```
#### Database Optimization Setup
**Step 4: Create Strategic Indexes**
```sql
-- Composite indexes for common query patterns
CREATE INDEX idx_tasks_project_status ON tasks(project, status);
CREATE INDEX idx_tasks_assigned_status ON tasks(assigned_to, status) WHERE assigned_to IS NOT NULL;
CREATE INDEX idx_tasks_due_date ON tasks(due_date) WHERE due_date IS NOT NULL;
-- Partial indexes for active data
CREATE INDEX idx_active_tasks ON tasks(project, status)
WHERE status NOT IN (
SELECT id FROM task_statuses WHERE is_final = true
);
-- Full-text search optimization
CREATE INDEX idx_tasks_search ON tasks
USING gin(to_tsvector('english', title || ' ' || COALESCE(description, '')));
-- Project hierarchy optimization
CREATE INDEX idx_projects_parent ON projects(parent_project) WHERE parent_project IS NOT NULL;
CREATE INDEX idx_tasks_parent ON tasks(parent_task) WHERE parent_task IS NOT NULL;
```
### 1.2 MCP Tools Implementation
#### MCP Server Extension Setup
**Step 1: Project Structure Setup**
```bash
# Create MCP extension directory
mkdir -p tools/directus-task-management-mcp
cd tools/directus-task-management-mcp
# Initialize package.json
npm init -y
npm install @modelcontextprotocol/sdk-typescript
npm install @types/node typescript --save-dev
# Create source structure
mkdir -p src/{tools,types,utils}
```
**Step 2: Core MCP Tools Implementation**
**`src/tools/task-crud.ts`**
```typescript
import { Tool } from "@modelcontextprotocol/sdk-typescript";
import { DirectusClient } from "../utils/directus-client";
import { Task, TaskCreateData, TaskUpdateData, TaskFilters } from "../types/task-types";
export const createTaskTool: Tool = {
name: "mcp__directus__create_task",
description: "Create a new task in Directus with full metadata support",
inputSchema: {
type: "object",
properties: {
data: {
type: "object",
properties: {
title: { type: "string" },
description: { type: "string" },
project: { type: "string", format: "uuid" },
priority: { type: "string", enum: ["lowest", "low", "medium", "high", "highest"] },
task_type: { type: "string", enum: ["feature", "bug", "enhancement", "research", "maintenance"] },
complexity: { type: "string", enum: ["trivial", "minor", "major", "critical"] },
assigned_to: { type: "string", format: "uuid" },
estimated_hours: { type: "number" },
due_date: { type: "string", format: "date" },
acceptance_criteria: { type: "string" },
definition_of_done: { type: "string" },
tags: { type: "array", items: { type: "string" } }
},
required: ["title", "project"]
}
},
required: ["data"]
}
};
export async function handleCreateTask(args: any): Promise<Task> {
const client = new DirectusClient();
try {
// Validate project exists
await client.validateProject(args.data.project);
// Get default status for new tasks
const defaultStatus = await client.getDefaultTaskStatus();
// Prepare task data with defaults
const taskData: TaskCreateData = {
...args.data,
status: defaultStatus.id,
progress_percentage: 0,
ai_generated: args.data.ai_context ? true : false,
created_at: new Date().toISOString()
};
// Create task
const task = await client.createItem('tasks', taskData);
// Handle tags if provided
if (args.data.tags?.length > 0) {
await client.manageTags(task.id, args.data.tags);
}
return task;
} catch (error) {
throw new Error(`Failed to create task: ${error.message}`);
}
}
```
**`src/tools/task-queries.ts`**
```typescript
export const getTaskTool: Tool = {
name: "mcp__directus__get_task",
description: "Get detailed task information with optional relations",
inputSchema: {
type: "object",
properties: {
id: { type: "string", format: "uuid" },
include_relations: { type: "boolean", default: false }
},
required: ["id"]
}
};
export async function handleGetTask(args: any): Promise<Task> {
const client = new DirectusClient();
const fields = args.include_relations ? [
'*',
'status.*',
'project.*',
'assigned_to.*',
'parent_task.*',
'dependencies.dependency_task.*',
'time_entries.*',
'ai_contexts.*'
] : ['*', 'status.*', 'project.name', 'assigned_to.first_name', 'assigned_to.last_name'];
try {
const task = await client.readItem('tasks', args.id, { fields });
if (!task) {
throw new Error(`Task with ID ${args.id} not found`);
}
return task;
} catch (error) {
throw new Error(`Failed to get task: ${error.message}`);
}
}
export const listTasksTool: Tool = {
name: "mcp__directus__list_tasks",
description: "List tasks with filtering, sorting, and pagination",
inputSchema: {
type: "object",
properties: {
filters: {
type: "object",
properties: {
project: { type: "string", format: "uuid" },
status: { type: "string" },
assigned_to: { type: "string", format: "uuid" },
priority: { type: "string" },
task_type: { type: "string" },
search: { type: "string" },
due_before: { type: "string", format: "date" },
created_after: { type: "string", format: "date-time" }
}
},
pagination: {
type: "object",
properties: {
limit: { type: "integer", minimum: 1, maximum: 100, default: 25 },
offset: { type: "integer", minimum: 0, default: 0 },
page: { type: "integer", minimum: 1 }
}
},
sort: {
type: "array",
items: { type: "string" },
default: ["-created_at"]
}
}
}
};
export async function handleListTasks(args: any): Promise<{ data: Task[], meta: any }> {
const client = new DirectusClient();
const query: any = {
fields: ['*', 'status.*', 'project.name', 'assigned_to.first_name', 'assigned_to.last_name'],
limit: args.pagination?.limit || 25,
sort: args.sort || ['-created_at']
};
// Handle pagination
if (args.pagination?.offset) {
query.offset = args.pagination.offset;
} else if (args.pagination?.page) {
query.offset = (args.pagination.page - 1) * query.limit;
}
// Build filter conditions
const filter: any = {};
if (args.filters) {
if (args.filters.project) filter.project = { _eq: args.filters.project };
if (args.filters.status) filter.status = { _eq: args.filters.status };
if (args.filters.assigned_to) filter.assigned_to = { _eq: args.filters.assigned_to };
if (args.filters.priority) filter.priority = { _eq: args.filters.priority };
if (args.filters.task_type) filter.task_type = { _eq: args.filters.task_type };
if (args.filters.due_before) filter.due_date = { _lte: args.filters.due_before };
if (args.filters.created_after) filter.created_at = { _gte: args.filters.created_after };
// Full-text search
if (args.filters.search) {
filter._or = [
{ title: { _icontains: args.filters.search } },
{ description: { _icontains: args.filters.search } }
];
}
}
if (Object.keys(filter).length > 0) {
query.filter = filter;
}
try {
const result = await client.readItems('tasks', query);
return result;
} catch (error) {
throw new Error(`Failed to list tasks: ${error.message}`);
}
}
```
**Step 3: AI Integration Tools**
**`src/tools/ai-integration.ts`**
```typescript
export const createAITaskTool: Tool = {
name: "mcp__directus__create_ai_task",
description: "Create a task using AI prompt processing with context awareness",
inputSchema: {
type: "object",
properties: {
prompt: { type: "string", minLength: 10 },
context: {
type: "object",
properties: {
project_id: { type: "string", format: "uuid" },
current_tasks: { type: "array", items: { type: "string" } },
agent_type: { type: "string" },
workflow_stage: { type: "string" }
}
}
},
required: ["prompt"]
}
};
export async function handleCreateAITask(args: any): Promise<Task> {
const client = new DirectusClient();
const aiService = new AITaskProcessor();
try {
// Get context information
let projectContext = null;
if (args.context?.project_id) {
projectContext = await client.readItem('projects', args.context.project_id);
}
// Process prompt with AI
const taskData = await aiService.processTaskPrompt(args.prompt, {
project: projectContext,
existingTasks: args.context?.current_tasks || [],
agentType: args.context?.agent_type,
workflowStage: args.context?.workflow_stage
});
// Enhance with AI context
taskData.ai_generated = true;
taskData.ai_context = {
original_prompt: args.prompt,
processing_timestamp: new Date().toISOString(),
agent_type: args.context?.agent_type,
confidence_score: taskData.confidence_score || 0.8
};
// Create task using standard creation flow
return await handleCreateTask({ data: taskData });
} catch (error) {
throw new Error(`Failed to create AI task: ${error.message}`);
}
}
export const updateTaskAIContextTool: Tool = {
name: "mcp__directus__update_task_ai_context",
description: "Update task AI context with agent activity and results",
inputSchema: {
type: "object",
properties: {
task_id: { type: "string", format: "uuid" },
context_data: {
type: "object",
properties: {
agent_type: { type: "string" },
operation: { type: "string" },
result: { type: "string" },
files_modified: { type: "array", items: { type: "string" } },
time_spent: { type: "number" },
success: { type: "boolean" },
next_steps: { type: "string" }
},
required: ["agent_type", "operation", "success"]
}
},
required: ["task_id", "context_data"]
}
};
export async function handleUpdateTaskAIContext(args: any): Promise<void> {
const client = new DirectusClient();
try {
// Create AI context entry
await client.createItem('task_ai_contexts', {
task: args.task_id,
ai_agent_type: args.context_data.agent_type,
context_type: 'feedback',
context_data: args.context_data,
execution_timestamp: new Date().toISOString(),
success: args.context_data.success
});
// Update task progress if specified
if (args.context_data.progress_update) {
await client.updateItem('tasks', args.task_id, {
progress_percentage: args.context_data.progress_update,
updated_at: new Date().toISOString()
});
}
} catch (error) {
throw new Error(`Failed to update task AI context: ${error.message}`);
}
}
```
### 1.3 Frontend Interface Setup
#### Directus Admin Interface Customization
**Step 1: Collection Interface Configuration**
**`collections/tasks.yaml`** (via API or admin interface)
```yaml
collection: tasks
meta:
icon: check_box
note: "Task management with full project integration"
display_template: "{{title}} - {{project.name}}"
fields:
title:
interface: input
options:
placeholder: "Enter task title..."
display_options:
tabular: true
required: true
description:
interface: input-rich-text-html
options:
toolbar: ['bold', 'italic', 'underline', 'link', 'code']
status:
interface: select-dropdown-m2o
options:
template: "{{name}}"
display: labels
display_options:
choices:
- background: "{{color}}"
foreground: "#ffffff"
value: "{{id}}"
text: "{{name}}"
priority:
interface: select-dropdown
display: labels
display_options:
choices:
- { text: "Lowest", value: "lowest", background: "#64748b" }
- { text: "Low", value: "low", background: "#10b981" }
- { text: "Medium", value: "medium", background: "#f59e0b" }
- { text: "High", value: "high", background: "#ef4444" }
- { text: "Highest", value: "highest", background: "#dc2626" }
project:
interface: select-dropdown-m2o
options:
template: "{{name}}"
required: true
assigned_to:
interface: select-dropdown-m2o
options:
template: "{{first_name}} {{last_name}}"
progress_percentage:
interface: slider
options:
min: 0
max: 100
step: 5
display: progress-bar
estimated_hours:
interface: input
options:
type: number
step: 0.25
min: 0
due_date:
interface: datetime
options:
type: date
```
**Step 2: Custom Layout Configuration**
**`layouts/tasks-detail.vue`**
```vue
<template>
<div class="task-detail-layout">
<!-- Header Section -->
<div class="task-header">
<v-card class="task-title-card">
<v-card-title>
<div class="task-title-section">
<v-field v-model="values.title" :field="titleField" />
<div class="task-meta">
<v-field v-model="values.status" :field="statusField" />
<v-field v-model="values.priority" :field="priorityField" />
</div>
</div>
</v-card-title>
</v-card>
</div>
<!-- Main Content Grid -->
<div class="task-content-grid">
<!-- Left Column: Task Details -->
<div class="task-details">
<v-card>
<v-card-title>Task Details</v-card-title>
<v-card-text>
<v-field v-model="values.description" :field="descriptionField" />
<v-field v-model="values.acceptance_criteria" :field="acceptanceCriteriaField" />
<v-field v-model="values.definition_of_done" :field="definitionOfDoneField" />
</v-card-text>
</v-card>
</div>
<!-- Right Column: Metadata -->
<div class="task-metadata">
<v-card class="mb-4">
<v-card-title>Assignment</v-card-title>
<v-card-text>
<v-field v-model="values.project" :field="projectField" />
<v-field v-model="values.assigned_to" :field="assignedToField" />
<v-field v-model="values.ai_agent_assigned" :field="aiAgentField" />
</v-card-text>
</v-card>
<v-card class="mb-4">
<v-card-title>Planning</v-card-title>
<v-card-text>
<v-field v-model="values.estimated_hours" :field="estimatedHoursField" />
<v-field v-model="values.story_points" :field="storyPointsField" />
<v-field v-model="values.complexity" :field="complexityField" />
<v-field v-model="values.due_date" :field="dueDateField" />
</v-card-text>
</v-card>
<v-card>
<v-card-title>Progress</v-card-title>
<v-card-text>
<v-field v-model="values.progress_percentage" :field="progressField" />
<div class="progress-stats">
<div>Estimated: {{ values.estimated_hours }}h</div>
<div>Actual: {{ values.actual_hours }}h</div>
</div>
</v-card-text>
</v-card>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'TaskDetailLayout',
props: {
collection: String,
primaryKey: [String, Number],
item: Object,
fields: Array
},
computed: {
values() {
return this.item || {};
},
titleField() {
return this.fields.find(f => f.field === 'title');
},
statusField() {
return this.fields.find(f => f.field === 'status');
},
// ... other field getters
}
};
</script>
<style scoped>
.task-detail-layout {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.task-header {
margin-bottom: 24px;
}
.task-content-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 24px;
}
.task-title-section {
width: 100%;
}
.task-meta {
display: flex;
gap: 16px;
margin-top: 12px;
}
.progress-stats {
margin-top: 8px;
font-size: 14px;
color: var(--foreground-subdued);
}
@media (max-width: 768px) {
.task-content-grid {
grid-template-columns: 1fr;
}
}
</style>
```
## Phase 2: Integration Implementation Guide
### 2.1 Task Master Sync Integration
#### Bidirectional Sync Service
**`src/services/task-master-sync.ts`**
```typescript
import { EventEmitter } from 'events';
import { spawn } from 'child_process';
import { DirectusClient } from '../utils/directus-client';
export class TaskMasterSyncService extends EventEmitter {
private directus: DirectusClient;
private syncInterval: NodeJS.Timer | null = null;
private isRunning: boolean = false;
constructor() {
super();
this.directus = new DirectusClient();
}
async startSync(intervalMinutes: number = 5): Promise<void> {
if (this.isRunning) return;
this.isRunning = true;
console.log('Starting Task Master sync service...');
// Initial sync
await this.performSync();
// Set up recurring sync
this.syncInterval = setInterval(async () => {
try {
await this.performSync();
} catch (error) {
console.error('Sync error:', error);
this.emit('sync-error', error);
}
}, intervalMinutes * 60 * 1000);
this.emit('sync-started');
}
async stopSync(): Promise<void> {
if (this.syncInterval) {
clearInterval(this.syncInterval);
this.syncInterval = null;
}
this.isRunning = false;
this.emit('sync-stopped');
}
async performSync(): Promise<void> {
console.log('Starting bidirectional sync...');
const syncResult = {
timestamp: new Date().toISOString(),
taskMasterToDirectus: 0,
directusToTaskMaster: 0,
conflicts: 0,
errors: []
};
try {
// Phase 1: Task Master → Directus
const tmUpdates = await this.syncFromTaskMaster();
syncResult.taskMasterToDirectus = tmUpdates.length;
// Phase 2: Directus → Task Master
const directusUpdates = await this.syncToTaskMaster();
syncResult.directusToTaskMaster = directusUpdates.length;
// Phase 3: Conflict Resolution
const conflicts = await this.resolveConflicts();
syncResult.conflicts = conflicts.length;
console.log('Sync completed:', syncResult);
this.emit('sync-completed', syncResult);
} catch (error) {
syncResult.errors.push(error.message);
this.emit('sync-error', error);
throw error;
}
}
private async syncFromTaskMaster(): Promise<any[]> {
// Export from Task Master
const tmData = await this.executeTaskMasterCommand(['export', '--format=json', '--since-sync']);
if (!tmData.tasks || tmData.tasks.length === 0) {
return [];
}
const updates: any[] = [];
for (const tmTask of tmData.tasks) {
try {
// Check if task exists in Directus
const existingTask = await this.directus.readItems('tasks', {
filter: { task_master_id: { _eq: tmTask.id } },
limit: 1
});
if (existingTask.data.length > 0) {
// Update existing task
const directusTask = existingTask.data[0];
const updateData = this.mapTaskMasterToDirectus(tmTask);
// Check for conflicts
if (this.hasConflict(directusTask, updateData, tmTask)) {
await this.recordConflict(directusTask.id, tmTask, updateData);
continue;
}
await this.directus.updateItem('tasks', directusTask.id, updateData);
updates.push({ type: 'update', taskMasterId: tmTask.id, directusId: directusTask.id });
} else {
// Create new task
const newTaskData = this.mapTaskMasterToDirectus(tmTask);
newTaskData.task_master_id = tmTask.id;
const newTask = await this.directus.createItem('tasks', newTaskData);
updates.push({ type: 'create', taskMasterId: tmTask.id, directusId: newTask.id });
}
} catch (error) {
console.error(`Error syncing task ${tmTask.id}:`, error);
continue;
}
}
return updates;
}
private async syncToTaskMaster(): Promise<any[]> {
// Get tasks modified since last sync
const lastSync = await this.getLastSyncTimestamp();
const modifiedTasks = await this.directus.readItems('tasks', {
filter: {
_and: [
{ updated_at: { _gt: lastSync } },
{ task_master_id: { _nnull: true } }
]
}
});
const updates: any[] = [];
for (const directusTask of modifiedTasks.data) {
try {
const tmData = this.mapDirectusToTaskMaster(directusTask);
// Update in Task Master
await this.executeTaskMasterCommand([
'update-task',
'--id', directusTask.task_master_id,
'--data', JSON.stringify(tmData)
]);
updates.push({
type: 'update',
directusId: directusTask.id,
taskMasterId: directusTask.task_master_id
});
} catch (error) {
console.error(`Error syncing to Task Master ${directusTask.task_master_id}:`, error);
continue;
}
}
return updates;
}
private mapTaskMasterToDirectus(tmTask: any): any {
return {
title: tmTask.title,
description: tmTask.description,
// Map Task Master status to Directus status
status: this.mapStatus(tmTask.status, 'tm_to_directus'),
priority: this.mapPriority(tmTask.priority, 'tm_to_directus'),
estimated_hours: tmTask.estimated_hours,
progress_percentage: tmTask.progress || 0,
due_date: tmTask.due_date,
acceptance_criteria: tmTask.acceptance_criteria,
definition_of_done: tmTask.definition_of_done,
updated_at: new Date().toISOString(),
// Preserve sync metadata
external_refs: {
...tmTask.external_refs,
last_tm_sync: new Date().toISOString()
}
};
}
private mapDirectusToTaskMaster(directusTask: any): any {
return {
title: directusTask.title,
description: directusTask.description,
status: this.mapStatus(directusTask.status.slug, 'directus_to_tm'),
priority: this.mapPriority(directusTask.priority, 'directus_to_tm'),
estimated_hours: directusTask.estimated_hours,
progress: directusTask.progress_percentage,
due_date: directusTask.due_date,
acceptance_criteria: directusTask.acceptance_criteria,
definition_of_done: directusTask.definition_of_done
};
}
private async executeTaskMasterCommand(args: string[]): Promise<any> {
return new Promise((resolve, reject) => {
const process = spawn('task-master', args, { stdio: 'pipe' });
let output = '';
process.stdout.on('data', (data) => {
output += data.toString();
});
process.on('close', (code) => {
if (code === 0) {
try {
resolve(JSON.parse(output));
} catch (error) {
resolve({ output });
}
} else {
reject(new Error(`Task Master command failed with code ${code}`));
}
});
});
}
private hasConflict(directusTask: any, updateData: any, tmTask: any): boolean {
// Simple timestamp-based conflict detection
const directusModified = new Date(directusTask.updated_at);
const tmModified = new Date(tmTask.updated_at);
const lastSync = new Date(directusTask.external_refs?.last_tm_sync || 0);
// Conflict if both have been modified since last sync
return directusModified > lastSync && tmModified > lastSync;
}
private async recordConflict(directusTaskId: string, tmTask: any, updateData: any): Promise<void> {
await this.directus.createItem('task_sync_conflicts', {
task: directusTaskId,
conflict_type: 'bidirectional_update',
directus_data: updateData,
task_master_data: tmTask,
detected_at: new Date().toISOString(),
resolution_status: 'pending'
});
}
private mapStatus(status: string, direction: 'tm_to_directus' | 'directus_to_tm'): string {
const statusMap = {
tm_to_directus: {
'pending': 'todo',
'in-progress': 'in_progress',
'done': 'completed',
'blocked': 'blocked'
},
directus_to_tm: {
'todo': 'pending',
'in_progress': 'in-progress',
'completed': 'done',
'blocked': 'blocked'
}
};
return statusMap[direction][status] || status;
}
private async getLastSyncTimestamp(): Promise<string> {
const syncRecord = await this.directus.readItems('sync_metadata', {
filter: { sync_type: { _eq: 'task_master' } },
sort: ['-created_at'],
limit: 1
});
return syncRecord.data[0]?.last_sync_time || new Date(0).toISOString();
}
}
```
### 2.2 BMad Integration Implementation
#### BMad Workflow Templates
**`src/services/bmad-integration.ts`**
```typescript
export class BMadIntegrationService {
private directus: DirectusClient;
constructor() {
this.directus = new DirectusClient();
}
async createBMadEpic(epicData: BMadEpicData): Promise<Task> {
// Create epic task
const epic = await this.directus.createItem('tasks', {
title: epicData.title,
description: epicData.description,
task_type: 'feature',
priority: 'high',
complexity: 'major',
project: epicData.project_id,
bmad_story_id: epicData.bmad_id,
ai_generated: true,
ai_context: {
bmad_workflow: 'epic_creation',
planning_phase: epicData.planning_phase,
methodology: 'bmad'
}
});
// Create workflow tracking entries
await this.initializeBMadWorkflow(epic.id, epicData.workflow_type);
return epic;
}
async generateUserStories(epicId: string, storyCount: number = 3): Promise<Task[]> {
const epic = await this.directus.readItem('tasks', epicId);
if (!epic) throw new Error('Epic not found');
const stories: Task[] = [];
for (let i = 1; i <= storyCount; i++) {
const story = await this.directus.createItem('tasks', {
title: `${epic.title} - User Story ${i}`,
description: `User story derived from epic: ${epic.title}`,
task_type: 'feature',
priority: epic.priority,
complexity: 'minor',
project: epic.project,
parent_task: epic.id,
epic: epic.id,
bmad_story_id: `${epic.bmad_story_id}-story-${i}`,
ai_generated: true,
ai_context: {
bmad_workflow: 'story_generation',
parent_epic: epic.id,
story_number: i
}
});
stories.push(story);
}
return stories;
}
private async initializeBMadWorkflow(taskId: string, workflowType: string): Promise<void> {
const workflows = this.getBMadWorkflowSteps(workflowType);
for (const [index, step] of workflows.entries()) {
await this.directus.createItem('task_bmad_workflows', {
task: taskId,
bmad_phase: step.phase,
bmad_agent: step.agent,
workflow_step: step.step,
step_status: 'pending',
execution_order: index + 1,
step_data: step.default_data || {}
});
}
}
private getBMadWorkflowSteps(workflowType: string): BMadWorkflowStep[] {
const workflows = {
greenfield: [
{
phase: 'analysis',
agent: 'pm',
step: 'requirements_gathering',
default_data: { templates: ['prd', 'user_stories'] }
},
{
phase: 'analysis',
agent: 'architect',
step: 'system_design',
default_data: { deliverables: ['architecture_diagram', 'tech_stack'] }
},
{
phase: 'planning',
agent: 'analyst',
step: 'research_analysis',
default_data: { research_areas: ['market', 'technical', 'competitive'] }
},
{
phase: 'development',
agent: 'dev',
step: 'implementation',
default_data: { coding_standards: true, tdd_required: true }
},
{
phase: 'review',
agent: 'qa',
step: 'quality_assurance',
default_data: { test_coverage_min: 80, security_review: true }
}
],
brownfield: [
{
phase: 'analysis',
agent: 'architect',
step: 'system_assessment',
default_data: { legacy_analysis: true, impact_assessment: true }
},
{
phase: 'planning',
agent: 'pm',
step: 'enhancement_planning',
default_data: { risk_assessment: true, rollback_plan: true }
},
{
phase: 'development',
agent: 'dev',
step: 'incremental_implementation',
default_data: { backwards_compatibility: true, feature_flags: true }
},
{
phase: 'review',
agent: 'qa',
step: 'regression_testing',
default_data: { full_regression: true, performance_testing: true }
}
]
};
return workflows[workflowType] || workflows.greenfield;
}
async updateWorkflowStep(taskId: string, stepId: string, stepData: any): Promise<void> {
await this.directus.updateItem('task_bmad_workflows', stepId, {
step_status: stepData.status,
step_data: { ...stepData.data },
completed_at: stepData.status === 'completed' ? new Date().toISOString() : null
});
// Check if all steps are complete
if (stepData.status === 'completed') {
const allSteps = await this.directus.readItems('task_bmad_workflows', {
filter: { task: { _eq: taskId } }
});
const allComplete = allSteps.data.every(step => step.step_status === 'completed');
if (allComplete) {
await this.directus.updateItem('tasks', taskId, {
status: await this.getStatusId('completed'),
progress_percentage: 100,
completed_at: new Date().toISOString()
});
}
}
}
}
```
This implementation guide provides comprehensive code examples and setup instructions for the first two phases of the Directus Task Management Suite. The remaining phases would follow similar patterns with additional features like analytics dashboards, advanced AI integration, and performance optimizations.
The key to successful implementation is:
1. **Start with solid foundations** - proper database design and API patterns
2. **Build incrementally** - each phase delivers working functionality
3. **Test thoroughly** - especially integration points with existing systems
4. **Document extensively** - enable future maintenance and enhancement
5. **Monitor performance** - ensure scalability as usage grows
Each code example includes error handling, validation, and integration patterns that align with the existing project architecture and established development practices.