Skip to content

Commit b625c12

Browse files
committed
refactor(tasks): enhance task formatting and error handling
Improve task list and detail formatting by adding comprehensive fields such as progress, keys, words, and source language to provide richer task snapshots. Introduce detailed language assignment formatting including user and group listings with leverage info. Refactor error handling in task tool functions to return formatted errors instead of throwing, enhancing robustness and consistent error propagation. These changes improve clarity and completeness of task reporting and stabilize task tool error flows.
1 parent 91bf304 commit b625c12

2 files changed

Lines changed: 199 additions & 41 deletions

File tree

src/domains/tasks/tasks.formatter.ts

Lines changed: 194 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ export function formatTasksList(
7070
title: task.title || "Untitled",
7171
status: task.status || "Unknown",
7272
type: task.task_type || "Unknown",
73+
sourceLanguage: task.source_language_iso || "Unknown",
74+
progress: typeof task.progress === "number" ? `${task.progress}%` : "N/A",
75+
keys: typeof task.keys_count === "number" ? task.keys_count : "N/A",
76+
words: typeof task.words_count === "number" ? task.words_count : "N/A",
7377
dueDate: formatDueDate(task.due_date),
7478
created: formatCreatedDate(task.created_at),
7579
}));
@@ -79,13 +83,27 @@ export function formatTasksList(
7983
{ key: "title", header: "Title", maxWidth: 30 },
8084
{ key: "status", header: "Status" },
8185
{ key: "type", header: "Type" },
86+
{ key: "progress", header: "Progress" },
87+
{ key: "keys", header: "Keys" },
88+
{ key: "words", header: "Words" },
8289
{ key: "dueDate", header: "Due Date" },
8390
{ key: "created", header: "Created" },
8491
]);
8592

8693
lines.push(table);
8794
lines.push("");
8895

96+
// Detailed snapshots per task covering all fields
97+
lines.push(formatHeading("Detailed Task Snapshots", 2));
98+
lines.push("");
99+
for (const task of tasks) {
100+
lines.push(
101+
formatHeading(`Task #${task.task_id}${task.title || "Untitled"}`, 3),
102+
);
103+
lines.push(formatTaskSnapshot(task));
104+
lines.push("");
105+
}
106+
89107
// Summary
90108
lines.push(formatHeading("Summary", 2));
91109
lines.push("");
@@ -135,6 +153,11 @@ export function formatTaskDetails(task: Task, projectId: string): string {
135153
Description: task.description || "*No description provided*",
136154
Type: task.task_type || "Unknown",
137155
Status: task.status || "Unknown",
156+
"Can Be Parent": boolToYesNo(task.can_be_parent),
157+
"Parent Task ID": task.parent_task_id ?? "None",
158+
"Closing Tags": formatArray(task.closing_tags),
159+
"Lock Translations": boolToYesNo(task.do_lock_translations),
160+
"Custom Status IDs": formatArray(task.custom_translation_status_ids),
138161
};
139162
lines.push(formatBulletList(coreInfo));
140163
lines.push("");
@@ -153,59 +176,53 @@ export function formatTaskDetails(task: Task, projectId: string): string {
153176
lines.push("");
154177
}
155178

156-
// Language assignments
179+
// Language assignments (detailed)
157180
lines.push(formatHeading("Language Assignments", 2));
158181
if (task.languages && task.languages.length > 0) {
159182
lines.push(`**Target Languages:** ${task.languages.length}`);
160183
lines.push("");
161-
162184
for (const language of task.languages) {
163-
lines.push(formatHeading(language.language_iso.toUpperCase(), 3));
164-
lines.push("");
165-
166-
if (language.users && language.users.length > 0) {
167-
lines.push(`**Assigned Users (${language.users.length}):**`);
168-
for (const user of language.users) {
169-
lines.push(
170-
`- ${user.fullname || user.email || `ID: ${user.user_id}`}`,
171-
);
172-
}
173-
lines.push("");
174-
}
175-
176-
if (language.groups && language.groups.length > 0) {
177-
lines.push(`**Assigned Groups (${language.groups.length}):**`);
178-
for (const group of language.groups) {
179-
lines.push(`- ${group.name || `ID: ${group.id}`}`);
180-
}
181-
lines.push("");
182-
}
183-
184-
if (
185-
(!language.users || language.users.length === 0) &&
186-
(!language.groups || language.groups.length === 0)
187-
) {
188-
lines.push("*No specific users or groups assigned to this language*");
189-
lines.push("");
190-
}
185+
lines.push(formatLanguageDetails(language));
191186
}
192187
} else {
193188
lines.push("*No languages assigned to this task*");
194189
lines.push("");
195190
}
196191

197-
// Task configuration
198-
lines.push(formatHeading("Task Configuration", 2));
192+
// Task configuration & metrics
193+
lines.push(formatHeading("Configuration & Metrics", 2));
199194
const configInfo: Record<string, unknown> = {
200195
"Source Language": task.source_language_iso || "Not specified",
201-
"Auto-close Languages": task.auto_close_languages ? "Yes" : "No",
202-
"Auto-close Task": task.auto_close_task ? "Yes" : "No",
203-
"Auto-close Items": task.auto_close_items ? "Yes" : "No",
204-
"Lock Translations": task.do_lock_translations ? "Yes" : "No",
196+
"Auto-close Languages": boolToYesNo(task.auto_close_languages),
197+
"Auto-close Task": boolToYesNo(task.auto_close_task),
198+
"Auto-close Items": boolToYesNo(task.auto_close_items),
199+
"Total Keys": task.keys_count ?? 0,
200+
"Total Words": task.words_count ?? 0,
201+
"Overall Progress":
202+
typeof task.progress === "number" ? `${task.progress}%` : "N/A",
205203
};
206204
lines.push(formatBulletList(configInfo));
207205
lines.push("");
208206

207+
// Audit & completion
208+
lines.push(formatHeading("Audit", 2));
209+
const auditInfo: Record<string, unknown> = {
210+
"Created By": task.created_by ?? "Unknown",
211+
"Created By Email": task.created_by_email ?? "Unknown",
212+
"Created At": task.created_at
213+
? formatCreatedDateWithTime(task.created_at)
214+
: "Unknown",
215+
"Created At (ts)": task.created_at_timestamp ?? "N/A",
216+
"Completed By": task.completed_by ?? "N/A",
217+
"Completed By Email": task.completed_by_email ?? "N/A",
218+
"Completed At": task.completed_at
219+
? formatCreatedDateWithTime(task.completed_at)
220+
: "N/A",
221+
"Completed At (ts)": task.completed_at_timestamp ?? "N/A",
222+
};
223+
lines.push(formatBulletList(auditInfo));
224+
lines.push("");
225+
209226
// Summary for LLM reasoning
210227
lines.push(formatHeading("Task Summary", 2));
211228
lines.push("**Task Characteristics:**");
@@ -586,3 +603,144 @@ function formatScheduleInfo(task: Task): string {
586603
lines.push("");
587604
return lines.join("\n");
588605
}
606+
607+
// --- Additional rich formatters ---
608+
609+
function boolToYesNo(value?: boolean): string {
610+
return value ? "Yes" : "No";
611+
}
612+
613+
function formatArray(arr?: Array<string | number> | null): string {
614+
if (!arr || arr.length === 0) return "None";
615+
return arr.join(", ");
616+
}
617+
618+
function formatTaskSnapshot(task: Task): string {
619+
const lines: string[] = [];
620+
621+
const meta: Record<string, unknown> = {
622+
Status: task.status || "Unknown",
623+
Type: task.task_type || "Unknown",
624+
Progress: typeof task.progress === "number" ? `${task.progress}%` : "N/A",
625+
"Due Date": task.due_date
626+
? formatDueDateWithTime(task.due_date)
627+
: "No deadline",
628+
"Due Date (ts)": task.due_date_timestamp ?? "N/A",
629+
"Created At": task.created_at
630+
? formatCreatedDateWithTime(task.created_at)
631+
: "Unknown",
632+
"Created At (ts)": task.created_at_timestamp ?? "N/A",
633+
"Created By": task.created_by ?? "Unknown",
634+
"Created By Email": task.created_by_email ?? "Unknown",
635+
"Completed At": task.completed_at
636+
? formatCreatedDateWithTime(task.completed_at)
637+
: "N/A",
638+
"Completed At (ts)": task.completed_at_timestamp ?? "N/A",
639+
"Completed By": task.completed_by ?? "N/A",
640+
"Completed By Email": task.completed_by_email ?? "N/A",
641+
"Can Be Parent": boolToYesNo(task.can_be_parent),
642+
"Parent Task ID": task.parent_task_id ?? "None",
643+
"Closing Tags": formatArray(task.closing_tags),
644+
"Lock Translations": boolToYesNo(task.do_lock_translations),
645+
"Custom Status IDs": formatArray(task.custom_translation_status_ids),
646+
"Source Language": task.source_language_iso || "Not specified",
647+
"Auto-close Languages": boolToYesNo(task.auto_close_languages),
648+
"Auto-close Task": boolToYesNo(task.auto_close_task),
649+
"Auto-close Items": boolToYesNo(task.auto_close_items),
650+
"Total Keys": task.keys_count ?? 0,
651+
"Total Words": task.words_count ?? 0,
652+
"Target Languages": task.languages?.length ?? 0,
653+
};
654+
655+
lines.push(formatBulletList(meta));
656+
lines.push("");
657+
658+
if (task.languages && task.languages.length > 0) {
659+
lines.push("**Languages:**");
660+
for (const language of task.languages) {
661+
lines.push(
662+
`- ${language.language_iso.toUpperCase()} • status: ${language.status || "Unknown"} • progress: ${typeof language.progress === "number" ? `${language.progress}%` : "N/A"} • keys: ${language.keys_count ?? 0} • words: ${language.words_count ?? 0}`,
663+
);
664+
}
665+
lines.push("");
666+
}
667+
668+
return lines.join("\n");
669+
}
670+
671+
function formatLanguageDetails(language: Task["languages"][number]): string {
672+
const lines: string[] = [];
673+
lines.push(formatHeading(language.language_iso.toUpperCase(), 3));
674+
lines.push("");
675+
676+
const langInfo: Record<string, unknown> = {
677+
Status: language.status || "Unknown",
678+
Progress:
679+
typeof language.progress === "number" ? `${language.progress}%` : "N/A",
680+
"Keys Count": language.keys_count ?? 0,
681+
"Words Count": language.words_count ?? 0,
682+
"Completed At": language.completed_at || "N/A",
683+
"Completed At (ts)": language.completed_at_timestamp ?? "N/A",
684+
"Completed By": language.completed_by ?? "N/A",
685+
"Completed By Email": language.completed_by_email ?? "N/A",
686+
};
687+
lines.push(formatBulletList(langInfo));
688+
lines.push("");
689+
690+
if (Array.isArray(language.keys) && language.keys.length > 0) {
691+
lines.push(`Keys scope: ${language.keys.length} key(s)`);
692+
}
693+
694+
if (language.users && language.users.length > 0) {
695+
lines.push(`Assigned Users (${language.users.length}):`);
696+
for (const user of language.users) {
697+
lines.push(`- ${user.fullname || user.email || `ID: ${user.user_id}`}`);
698+
}
699+
lines.push("");
700+
}
701+
702+
if (language.groups && language.groups.length > 0) {
703+
lines.push(`Assigned Groups (${language.groups.length}):`);
704+
for (const group of language.groups) {
705+
lines.push(`- ${group.name || `ID: ${group.id}`}`);
706+
}
707+
lines.push("");
708+
}
709+
710+
// Leverage
711+
if (language.initial_tm_leverage) {
712+
lines.push("Initial TM Leverage:");
713+
lines.push(formatLeverageBuckets(language.initial_tm_leverage));
714+
lines.push("");
715+
}
716+
if (language.tm_leverage) {
717+
lines.push(
718+
`TM Leverage Status: ${language.tm_leverage.status || "Unknown"}`,
719+
);
720+
if (language.tm_leverage.value) {
721+
lines.push(formatLeverageBuckets(language.tm_leverage.value));
722+
}
723+
lines.push("");
724+
}
725+
726+
if (
727+
(!language.users || language.users.length === 0) &&
728+
(!language.groups || language.groups.length === 0)
729+
) {
730+
lines.push("*No specific users or groups assigned to this language*");
731+
lines.push("");
732+
}
733+
734+
return lines.join("\n");
735+
}
736+
737+
function formatLeverageBuckets(value: Record<string, number>): string {
738+
const orderedKeys = ["0%+", "50%+", "60%+", "75%+", "85%+", "95%+", "100%"];
739+
const present = orderedKeys.filter((k) => k in value);
740+
if (present.length === 0) return "- No leverage data";
741+
const lines: string[] = [];
742+
for (const k of present) {
743+
lines.push(`- ${k}: ${value[k]}%`);
744+
}
745+
return lines.join("\n");
746+
}

src/domains/tasks/tasks.tool.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ async function handleListTasks(args: ListTasksToolArgsType) {
5656
};
5757
} catch (error) {
5858
methodLogger.error("Error in handleListTasks", { error, args });
59-
throw formatErrorForMcpTool(error);
59+
return formatErrorForMcpTool(error);
6060
}
6161
}
6262

@@ -92,7 +92,7 @@ async function handleCreateTask(args: CreateTaskToolArgsType) {
9292
};
9393
} catch (error) {
9494
methodLogger.error("Error in handleCreateTask", { error, args });
95-
throw formatErrorForMcpTool(error);
95+
return formatErrorForMcpTool(error);
9696
}
9797
}
9898

@@ -128,7 +128,7 @@ async function handleGetTask(args: GetTaskToolArgsType) {
128128
};
129129
} catch (error) {
130130
methodLogger.error("Error in handleGetTask", { error, args });
131-
throw formatErrorForMcpTool(error);
131+
return formatErrorForMcpTool(error);
132132
}
133133
}
134134

@@ -164,7 +164,7 @@ async function handleUpdateTask(args: UpdateTaskToolArgsType) {
164164
};
165165
} catch (error) {
166166
methodLogger.error("Error in handleUpdateTask", { error, args });
167-
throw formatErrorForMcpTool(error);
167+
return formatErrorForMcpTool(error);
168168
}
169169
}
170170

@@ -200,7 +200,7 @@ async function handleDeleteTask(args: DeleteTaskToolArgsType) {
200200
};
201201
} catch (error) {
202202
methodLogger.error("Error in handleDeleteTask", { error, args });
203-
throw formatErrorForMcpTool(error);
203+
return formatErrorForMcpTool(error);
204204
}
205205
}
206206

0 commit comments

Comments
 (0)