/**
* Developed by eBrook Group.
* Copyright © 2026 eBrook Group (https://www.ebrook.com.tw)
*/
/**
* Analyze workflow tool handler
* Analyzes task workflow and provides insights
*/
import { ClickUpService } from "../services/clickup.js";
import { debug, error } from "../utils/logger.js";
import { sanitizeErrorResponse } from "../utils/error-sanitizer.js";
import type { AppConfig } from "../config/env.js";
import type { ClickUpTaskResponse } from "../types/index.js";
/**
* Analyze workflow tool handler
* @param task_id - ClickUp task ID
* @param _config - Application configuration (unused)
* @param click_up_service - ClickUp service instance
* @returns Tool execution result
*/
export async function handleAnalyzeWorkflow(
task_id: string,
_config: AppConfig,
click_up_service: ClickUpService
): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> {
debug(`Analyzing workflow for task: ${task_id}`);
// Fetch task from ClickUp
const task_res = await click_up_service.getTask(task_id);
debug(`ClickUp API response status: ${task_res.status}`);
if (task_res.status !== 200) {
// Sanitized error message for client
const error_msg = `Failed to fetch task ${task_id}. ${sanitizeErrorResponse(task_res.status, task_res.body)}`;
// Full error details only in logs
debug(`Full API error response: ${JSON.stringify(task_res.body)}`);
error(error_msg);
return {
content: [
{
type: "text",
text: error_msg,
},
],
isError: true,
};
}
const task = task_res.body as ClickUpTaskResponse;
debug(`Successfully fetched task: ${task.name}`);
// Analyze workflow
const analysis = click_up_service.analyzeTaskWorkflow(task);
// Build formatted output
const output_lines: string[] = [];
output_lines.push("=".repeat(80));
output_lines.push(`📊 WORKFLOW ANALYSIS: ${analysis.task_name}`);
output_lines.push("=".repeat(80));
output_lines.push("");
// Status and Progress
output_lines.push("📈 STATUS & PROGRESS");
output_lines.push(` Current Status: ${analysis.current_status}`);
output_lines.push(` Progress: ${analysis.progress_percentage}%`);
const progress_bar = "█".repeat(Math.floor(analysis.progress_percentage / 5)) +
"░".repeat(20 - Math.floor(analysis.progress_percentage / 5));
output_lines.push(` [${progress_bar}] ${analysis.progress_percentage}%`);
output_lines.push("");
// Time Analysis
output_lines.push("⏰ TIME ANALYSIS");
output_lines.push(` Created: ${new Date(analysis.time_analysis.created_at).toLocaleString()}`);
output_lines.push(` Last Updated: ${new Date(analysis.time_analysis.last_updated).toLocaleString()}`);
output_lines.push(` Days Since Creation: ${analysis.time_analysis.days_since_creation} days`);
output_lines.push(` Days Since Update: ${analysis.time_analysis.days_since_update} days`);
if (analysis.time_analysis.estimated_hours !== null) {
output_lines.push(` Estimated Time: ${analysis.time_analysis.estimated_hours.toFixed(2)} hours`);
}
if (analysis.time_analysis.spent_hours !== null) {
output_lines.push(` Time Spent: ${analysis.time_analysis.spent_hours.toFixed(2)} hours`);
}
if (analysis.time_analysis.remaining_hours !== null) {
output_lines.push(` Remaining Time: ${analysis.time_analysis.remaining_hours.toFixed(2)} hours`);
}
output_lines.push("");
// Team Analysis
output_lines.push("👥 TEAM ANALYSIS");
output_lines.push(` Creator: ${analysis.team_analysis.creator}`);
if (analysis.team_analysis.assignees.length > 0) {
output_lines.push(` Assignees (${analysis.team_analysis.assignees.length}):`);
analysis.team_analysis.assignees.forEach((assignee, index) => {
output_lines.push(` ${index + 1}. ${assignee}`);
});
} else {
output_lines.push(` Assignees: ⚠️ No assignees`);
}
output_lines.push(` Watchers: ${analysis.team_analysis.watchers_count}`);
output_lines.push("");
// Dependency Analysis
output_lines.push("🔗 DEPENDENCY ANALYSIS");
output_lines.push(` Depends On: ${analysis.dependency_analysis.depends_on_count} task(s)`);
if (analysis.dependency_analysis.depends_on_tasks.length > 0) {
analysis.dependency_analysis.depends_on_tasks.forEach((dep_id, index) => {
output_lines.push(` ${index + 1}. Task ID: ${dep_id}`);
});
}
output_lines.push(` Blocked By: ${analysis.dependency_analysis.blocked_by_count} task(s)`);
output_lines.push(` Blocking: ${analysis.dependency_analysis.blocking_count} task(s)`);
output_lines.push("");
// Subtask Analysis
output_lines.push("📝 SUBTASK ANALYSIS");
output_lines.push(` Total Subtasks: ${analysis.subtask_analysis.total_subtasks}`);
if (analysis.subtask_analysis.total_subtasks > 0) {
output_lines.push(` Completed: ${analysis.subtask_analysis.completed_subtasks}`);
output_lines.push(` Pending: ${analysis.subtask_analysis.pending_subtasks}`);
}
output_lines.push("");
// Risk Factors
if (analysis.risk_factors.length > 0) {
output_lines.push("⚠️ RISK FACTORS");
analysis.risk_factors.forEach((risk, index) => {
output_lines.push(` ${index + 1}. ${risk}`);
});
output_lines.push("");
} else {
output_lines.push("✅ NO RISK FACTORS IDENTIFIED");
output_lines.push("");
}
// Recommendations
if (analysis.recommendations.length > 0) {
output_lines.push("💡 RECOMMENDATIONS");
analysis.recommendations.forEach((rec, index) => {
output_lines.push(` ${index + 1}. ${rec}`);
});
output_lines.push("");
} else {
output_lines.push("✨ TASK IS WELL CONFIGURED");
output_lines.push("");
}
// Overall Assessment
output_lines.push("🎯 OVERALL ASSESSMENT");
const risk_level =
analysis.risk_factors.length === 0 ? "LOW ✅" :
analysis.risk_factors.length <= 2 ? "MEDIUM ⚠️" :
"HIGH 🚨";
output_lines.push(` Risk Level: ${risk_level}`);
output_lines.push(` Health Score: ${calculateHealthScore(analysis)}/100`);
output_lines.push("");
output_lines.push("=".repeat(80));
// Return formatted result
return {
content: [
{
type: "text",
text: output_lines.join("\n"),
},
{
type: "text",
text: `\n📊 Detailed Analysis (JSON):\n${JSON.stringify(analysis, null, 2)}`,
},
],
isError: false,
};
}
/**
* Calculate task health score based on analysis
* @param analysis - Workflow analysis result
* @returns Health score (0-100)
*/
function calculateHealthScore(analysis: import("../types/index.js").TaskWorkflowAnalysis): number {
let score = 100;
// Deduct points for risk factors
score -= analysis.risk_factors.length * 10;
// Deduct points for missing assignees
if (analysis.team_analysis.assignees.length === 0) {
score -= 15;
}
// Deduct points for stale updates
if (analysis.time_analysis.days_since_update > 7) {
score -= 10;
}
// Deduct points for dependencies
score -= analysis.dependency_analysis.blocked_by_count * 5;
// Bonus points for watchers
score += Math.min(analysis.team_analysis.watchers_count * 2, 10);
// Ensure score is within 0-100 range
return Math.max(0, Math.min(100, score));
}