@task-mcp/shared 1.0.21 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -0
- package/package.json +1 -1
- package/src/algorithms/critical-path.ts +43 -38
- package/src/algorithms/dependency-integrity.ts +9 -15
- package/src/algorithms/tech-analysis.ts +27 -27
- package/src/algorithms/topological-sort.ts +71 -26
- package/src/schemas/index.ts +2 -13
- package/src/schemas/response-format.ts +6 -6
- package/src/schemas/response-schema.ts +25 -20
- package/src/schemas/task.ts +4 -22
- package/src/utils/dashboard-renderer.ts +27 -59
- package/src/utils/date.ts +2 -10
- package/src/utils/hierarchy.ts +4 -5
- package/src/utils/index.ts +12 -6
- package/src/utils/natural-language.ts +210 -83
- package/src/utils/projection.ts +8 -8
- package/src/utils/terminal-ui.ts +53 -54
- package/src/utils/workspace.ts +16 -4
|
@@ -28,26 +28,27 @@ const BaseResponse = z.object({
|
|
|
28
28
|
type: ResponseType.describe("Type of response: question, suggestion, or confirmation"),
|
|
29
29
|
message: z.string().describe("Main message to display to user"),
|
|
30
30
|
context: z.string().optional().describe("Additional context or explanation"),
|
|
31
|
-
priority: ResponsePriority.optional().describe(
|
|
31
|
+
priority: ResponsePriority.optional().describe(
|
|
32
|
+
"Urgency/importance of response (default: medium)"
|
|
33
|
+
),
|
|
32
34
|
});
|
|
33
35
|
|
|
34
36
|
// Question response - for clarification
|
|
35
37
|
export const QuestionResponse = BaseResponse.extend({
|
|
36
38
|
type: z.literal("question"),
|
|
37
|
-
options: z
|
|
39
|
+
options: z
|
|
40
|
+
.array(ResponseOption)
|
|
41
|
+
.optional()
|
|
38
42
|
.describe("Multiple choice options (omit for free-form answer)"),
|
|
39
|
-
defaultOption: z.string().optional()
|
|
40
|
-
.describe("Default option value if user provides no input"),
|
|
43
|
+
defaultOption: z.string().optional().describe("Default option value if user provides no input"),
|
|
41
44
|
});
|
|
42
45
|
export type QuestionResponse = z.infer<typeof QuestionResponse>;
|
|
43
46
|
|
|
44
47
|
// Suggestion response - for recommendations
|
|
45
48
|
export const SuggestionResponse = BaseResponse.extend({
|
|
46
49
|
type: z.literal("suggestion"),
|
|
47
|
-
options: z.array(ResponseOption).min(1)
|
|
48
|
-
|
|
49
|
-
reasoning: z.string().optional()
|
|
50
|
-
.describe("Explanation of why these suggestions were made"),
|
|
50
|
+
options: z.array(ResponseOption).min(1).describe("Suggested options for user to choose from"),
|
|
51
|
+
reasoning: z.string().optional().describe("Explanation of why these suggestions were made"),
|
|
51
52
|
});
|
|
52
53
|
export type SuggestionResponse = z.infer<typeof SuggestionResponse>;
|
|
53
54
|
|
|
@@ -55,9 +56,13 @@ export type SuggestionResponse = z.infer<typeof SuggestionResponse>;
|
|
|
55
56
|
export const ConfirmationResponse = BaseResponse.extend({
|
|
56
57
|
type: z.literal("confirmation"),
|
|
57
58
|
action: z.string().describe("Action that will be taken if confirmed"),
|
|
58
|
-
consequences: z
|
|
59
|
+
consequences: z
|
|
60
|
+
.array(z.string())
|
|
61
|
+
.optional()
|
|
59
62
|
.describe("List of effects or changes that will occur"),
|
|
60
|
-
defaultConfirm: z
|
|
63
|
+
defaultConfirm: z
|
|
64
|
+
.boolean()
|
|
65
|
+
.optional()
|
|
61
66
|
.describe("Default choice if user provides no input (default: false)"),
|
|
62
67
|
});
|
|
63
68
|
export type ConfirmationResponse = z.infer<typeof ConfirmationResponse>;
|
|
@@ -74,19 +79,19 @@ export type AgentResponse = z.infer<typeof AgentResponse>;
|
|
|
74
79
|
export const GenerateResponseInput = z.object({
|
|
75
80
|
type: ResponseType.describe("Type of response to generate"),
|
|
76
81
|
message: z.string().describe("Main message"),
|
|
77
|
-
options: z.array(ResponseOption).optional()
|
|
78
|
-
.describe("Options for question/suggestion types"),
|
|
82
|
+
options: z.array(ResponseOption).optional().describe("Options for question/suggestion types"),
|
|
79
83
|
context: z.string().optional(),
|
|
80
84
|
priority: ResponsePriority.optional().default("medium"),
|
|
81
|
-
action: z.string().optional()
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
action: z.string().optional().describe("Action description (for confirmation type)"),
|
|
86
|
+
consequences: z
|
|
87
|
+
.array(z.string())
|
|
88
|
+
.optional()
|
|
84
89
|
.describe("Consequences list (for confirmation type)"),
|
|
85
|
-
reasoning: z.string().optional()
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
.
|
|
89
|
-
|
|
90
|
+
reasoning: z.string().optional().describe("Reasoning explanation (for suggestion type)"),
|
|
91
|
+
defaultOption: z.string().optional().describe("Default option value (for question type)"),
|
|
92
|
+
defaultConfirm: z
|
|
93
|
+
.boolean()
|
|
94
|
+
.optional()
|
|
90
95
|
.describe("Default confirmation choice (for confirmation type)"),
|
|
91
96
|
});
|
|
92
97
|
export type GenerateResponseInput = z.infer<typeof GenerateResponseInput>;
|
package/src/schemas/task.ts
CHANGED
|
@@ -5,13 +5,7 @@ export const Priority = z.enum(["critical", "high", "medium", "low"]);
|
|
|
5
5
|
export type Priority = z.infer<typeof Priority>;
|
|
6
6
|
|
|
7
7
|
// Task status with clear state machine
|
|
8
|
-
export const TaskStatus = z.enum([
|
|
9
|
-
"pending",
|
|
10
|
-
"in_progress",
|
|
11
|
-
"blocked",
|
|
12
|
-
"completed",
|
|
13
|
-
"cancelled",
|
|
14
|
-
]);
|
|
8
|
+
export const TaskStatus = z.enum(["pending", "in_progress", "blocked", "completed", "cancelled"]);
|
|
15
9
|
export type TaskStatus = z.infer<typeof TaskStatus>;
|
|
16
10
|
|
|
17
11
|
// Dependency relationship types
|
|
@@ -37,25 +31,13 @@ export const TimeEstimate = z
|
|
|
37
31
|
.refine(
|
|
38
32
|
(data) => {
|
|
39
33
|
const { optimistic, expected, pessimistic } = data;
|
|
40
|
-
if (
|
|
41
|
-
optimistic !== undefined &&
|
|
42
|
-
expected !== undefined &&
|
|
43
|
-
optimistic > expected
|
|
44
|
-
) {
|
|
34
|
+
if (optimistic !== undefined && expected !== undefined && optimistic > expected) {
|
|
45
35
|
return false;
|
|
46
36
|
}
|
|
47
|
-
if (
|
|
48
|
-
expected !== undefined &&
|
|
49
|
-
pessimistic !== undefined &&
|
|
50
|
-
expected > pessimistic
|
|
51
|
-
) {
|
|
37
|
+
if (expected !== undefined && pessimistic !== undefined && expected > pessimistic) {
|
|
52
38
|
return false;
|
|
53
39
|
}
|
|
54
|
-
if (
|
|
55
|
-
optimistic !== undefined &&
|
|
56
|
-
pessimistic !== undefined &&
|
|
57
|
-
optimistic > pessimistic
|
|
58
|
-
) {
|
|
40
|
+
if (optimistic !== undefined && pessimistic !== undefined && optimistic > pessimistic) {
|
|
59
41
|
return false;
|
|
60
42
|
}
|
|
61
43
|
return true;
|
|
@@ -41,11 +41,13 @@ export interface DashboardStats {
|
|
|
41
41
|
export interface DependencyMetrics {
|
|
42
42
|
readyToWork: number;
|
|
43
43
|
blockedByDependencies: number;
|
|
44
|
-
mostDependedOn?:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
mostDependedOn?:
|
|
45
|
+
| {
|
|
46
|
+
id: string;
|
|
47
|
+
title: string;
|
|
48
|
+
dependentCount: number;
|
|
49
|
+
}
|
|
50
|
+
| undefined;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
export interface WorkspaceInfo {
|
|
@@ -107,9 +109,7 @@ export function calculateStats(tasks: Task[]): DashboardStats {
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
export function calculateDependencyMetrics(tasks: Task[]): DependencyMetrics {
|
|
110
|
-
const completedIds = new Set(
|
|
111
|
-
tasks.filter((t) => t.status === "completed").map((t) => t.id)
|
|
112
|
-
);
|
|
112
|
+
const completedIds = new Set(tasks.filter((t) => t.status === "completed").map((t) => t.id));
|
|
113
113
|
|
|
114
114
|
let readyToWork = 0;
|
|
115
115
|
let blockedByDependencies = 0;
|
|
@@ -134,10 +134,7 @@ export function calculateDependencyMetrics(tasks: Task[]): DependencyMetrics {
|
|
|
134
134
|
|
|
135
135
|
// Track dependent counts
|
|
136
136
|
for (const dep of deps) {
|
|
137
|
-
dependentCounts.set(
|
|
138
|
-
dep.taskId,
|
|
139
|
-
(dependentCounts.get(dep.taskId) ?? 0) + 1
|
|
140
|
-
);
|
|
137
|
+
dependentCounts.set(dep.taskId, (dependentCounts.get(dep.taskId) ?? 0) + 1);
|
|
141
138
|
}
|
|
142
139
|
}
|
|
143
140
|
|
|
@@ -192,8 +189,7 @@ export function renderStatusWidget(tasks: Task[]): string {
|
|
|
192
189
|
const today = getTodayTasks(tasks);
|
|
193
190
|
const overdue = getOverdueTasks(tasks);
|
|
194
191
|
const activeTasks = stats.total - stats.cancelled;
|
|
195
|
-
const percent =
|
|
196
|
-
activeTasks > 0 ? Math.round((stats.completed / activeTasks) * 100) : 0;
|
|
192
|
+
const percent = activeTasks > 0 ? Math.round((stats.completed / activeTasks) * 100) : 0;
|
|
197
193
|
|
|
198
194
|
const lines: string[] = [];
|
|
199
195
|
|
|
@@ -252,16 +248,10 @@ export function renderActionsWidget(tasks: Task[]): string {
|
|
|
252
248
|
|
|
253
249
|
// Get top 4 ready tasks sorted by priority
|
|
254
250
|
const readyTasks = tasks
|
|
255
|
-
.filter(
|
|
256
|
-
(t) =>
|
|
257
|
-
t.status === "pending" &&
|
|
258
|
-
(!t.dependencies || t.dependencies.length === 0)
|
|
259
|
-
)
|
|
251
|
+
.filter((t) => t.status === "pending" && (!t.dependencies || t.dependencies.length === 0))
|
|
260
252
|
.sort((a, b) => {
|
|
261
253
|
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
262
|
-
return (
|
|
263
|
-
(priorityOrder[a.priority] ?? 2) - (priorityOrder[b.priority] ?? 2)
|
|
264
|
-
);
|
|
254
|
+
return (priorityOrder[a.priority] ?? 2) - (priorityOrder[b.priority] ?? 2);
|
|
265
255
|
})
|
|
266
256
|
.slice(0, 4);
|
|
267
257
|
|
|
@@ -341,17 +331,13 @@ export function renderWorkspacesTable(
|
|
|
341
331
|
const stats = calculateStats(tasks);
|
|
342
332
|
const depMetrics = calculateDependencyMetrics(tasks);
|
|
343
333
|
const activeTasks = stats.total - stats.cancelled;
|
|
344
|
-
const percent =
|
|
345
|
-
activeTasks > 0
|
|
346
|
-
? Math.round((stats.completed / activeTasks) * 100)
|
|
347
|
-
: 0;
|
|
334
|
+
const percent = activeTasks > 0 ? Math.round((stats.completed / activeTasks) * 100) : 0;
|
|
348
335
|
|
|
349
336
|
// Create mini progress bar
|
|
350
337
|
const barWidth = 8;
|
|
351
338
|
const filled = Math.round((percent / 100) * barWidth);
|
|
352
339
|
const empty = barWidth - filled;
|
|
353
|
-
const miniBar =
|
|
354
|
-
c.green("█".repeat(filled)) + c.gray("░".repeat(empty));
|
|
340
|
+
const miniBar = c.green("█".repeat(filled)) + c.gray("░".repeat(empty));
|
|
355
341
|
|
|
356
342
|
rows.push({
|
|
357
343
|
name: truncateStr(ws.name, 20),
|
|
@@ -392,9 +378,7 @@ export const renderProjectsTable = renderWorkspacesTable;
|
|
|
392
378
|
* Render Tasks table for single workspace view
|
|
393
379
|
*/
|
|
394
380
|
export function renderTasksTable(tasks: Task[], limit: number = 10): string {
|
|
395
|
-
const activeTasks = tasks.filter(
|
|
396
|
-
(t) => t.status !== "completed" && t.status !== "cancelled"
|
|
397
|
-
);
|
|
381
|
+
const activeTasks = tasks.filter((t) => t.status !== "completed" && t.status !== "cancelled");
|
|
398
382
|
|
|
399
383
|
if (activeTasks.length === 0) {
|
|
400
384
|
return c.gray("No active tasks.");
|
|
@@ -421,10 +405,7 @@ export function renderTasksTable(tasks: Task[], limit: number = 10): string {
|
|
|
421
405
|
},
|
|
422
406
|
];
|
|
423
407
|
|
|
424
|
-
let result = table(
|
|
425
|
-
displayTasks as unknown as Record<string, unknown>[],
|
|
426
|
-
columns
|
|
427
|
-
);
|
|
408
|
+
let result = table(displayTasks as unknown as Record<string, unknown>[], columns);
|
|
428
409
|
|
|
429
410
|
if (activeTasks.length > limit) {
|
|
430
411
|
result += `\n${c.gray(`(+${activeTasks.length - limit} more tasks)`)}`;
|
|
@@ -440,21 +421,14 @@ export function renderTasksTable(tasks: Task[], limit: number = 10): string {
|
|
|
440
421
|
function getTodayTasks(tasks: Task[]): Task[] {
|
|
441
422
|
const today = new Date().toISOString().split("T")[0];
|
|
442
423
|
return tasks.filter(
|
|
443
|
-
(t) =>
|
|
444
|
-
t.dueDate === today &&
|
|
445
|
-
t.status !== "completed" &&
|
|
446
|
-
t.status !== "cancelled"
|
|
424
|
+
(t) => t.dueDate === today && t.status !== "completed" && t.status !== "cancelled"
|
|
447
425
|
);
|
|
448
426
|
}
|
|
449
427
|
|
|
450
428
|
function getOverdueTasks(tasks: Task[]): Task[] {
|
|
451
429
|
const today = new Date().toISOString().split("T")[0] ?? "";
|
|
452
430
|
return tasks.filter(
|
|
453
|
-
(t) =>
|
|
454
|
-
t.dueDate &&
|
|
455
|
-
t.dueDate < today &&
|
|
456
|
-
t.status !== "completed" &&
|
|
457
|
-
t.status !== "cancelled"
|
|
431
|
+
(t) => t.dueDate && t.dueDate < today && t.status !== "completed" && t.status !== "cancelled"
|
|
458
432
|
);
|
|
459
433
|
}
|
|
460
434
|
|
|
@@ -538,9 +512,7 @@ export function renderDashboard(
|
|
|
538
512
|
|
|
539
513
|
// Tasks table (for single workspace view)
|
|
540
514
|
if (showTasks && (currentWorkspace || workspaces.length === 1)) {
|
|
541
|
-
const activeTasks = tasks.filter(
|
|
542
|
-
(t) => t.status !== "completed" && t.status !== "cancelled"
|
|
543
|
-
);
|
|
515
|
+
const activeTasks = tasks.filter((t) => t.status !== "completed" && t.status !== "cancelled");
|
|
544
516
|
if (activeTasks.length > 0) {
|
|
545
517
|
lines.push(c.bold(`Tasks (${activeTasks.length})`));
|
|
546
518
|
lines.push("");
|
|
@@ -565,7 +537,7 @@ export function renderWorkspaceDashboard(
|
|
|
565
537
|
const wsInfo: WorkspaceInfo = {
|
|
566
538
|
name: workspace,
|
|
567
539
|
taskCount: tasks.length,
|
|
568
|
-
completedCount: tasks.filter(t => t.status === "completed").length,
|
|
540
|
+
completedCount: tasks.filter((t) => t.status === "completed").length,
|
|
569
541
|
};
|
|
570
542
|
|
|
571
543
|
const data: DashboardData = {
|
|
@@ -576,17 +548,13 @@ export function renderWorkspaceDashboard(
|
|
|
576
548
|
activeTag: options.activeTag,
|
|
577
549
|
};
|
|
578
550
|
|
|
579
|
-
return renderDashboard(
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
showTasks: true,
|
|
587
|
-
stripAnsiCodes: options.stripAnsiCodes,
|
|
588
|
-
}
|
|
589
|
-
);
|
|
551
|
+
return renderDashboard(data, () => tasks, {
|
|
552
|
+
showBanner: true,
|
|
553
|
+
showInbox: false,
|
|
554
|
+
showWorkspaces: false,
|
|
555
|
+
showTasks: true,
|
|
556
|
+
stripAnsiCodes: options.stripAnsiCodes,
|
|
557
|
+
});
|
|
590
558
|
}
|
|
591
559
|
|
|
592
560
|
// Legacy export for backwards compatibility
|
package/src/utils/date.ts
CHANGED
|
@@ -247,11 +247,7 @@ export function parseDateString(input: string): Date {
|
|
|
247
247
|
|
|
248
248
|
const d = new Date(input);
|
|
249
249
|
if (isNaN(d.getTime())) {
|
|
250
|
-
throw new DateParseError(
|
|
251
|
-
`Unable to parse "${input}" as a date`,
|
|
252
|
-
input,
|
|
253
|
-
"invalid_format"
|
|
254
|
-
);
|
|
250
|
+
throw new DateParseError(`Unable to parse "${input}" as a date`, input, "invalid_format");
|
|
255
251
|
}
|
|
256
252
|
|
|
257
253
|
return d;
|
|
@@ -280,11 +276,7 @@ export function formatDisplayDate(date: Date | string): string {
|
|
|
280
276
|
}
|
|
281
277
|
|
|
282
278
|
if (!isValidDate(d)) {
|
|
283
|
-
throw new DateParseError(
|
|
284
|
-
"Invalid Date object provided",
|
|
285
|
-
String(date),
|
|
286
|
-
"invalid_date"
|
|
287
|
-
);
|
|
279
|
+
throw new DateParseError("Invalid Date object provided", String(date), "invalid_date");
|
|
288
280
|
}
|
|
289
281
|
|
|
290
282
|
const parts = new Intl.DateTimeFormat(undefined, {
|
package/src/utils/hierarchy.ts
CHANGED
|
@@ -34,7 +34,9 @@ export function getTaskLevel(tasks: Task[], taskId: string): number {
|
|
|
34
34
|
while (currentTask.parentId) {
|
|
35
35
|
const parent = taskMap.get(currentTask.parentId);
|
|
36
36
|
if (!parent) {
|
|
37
|
-
console.warn(
|
|
37
|
+
console.warn(
|
|
38
|
+
`[task-mcp] Orphaned parent reference: task "${currentTask.id}" references non-existent parent "${currentTask.parentId}"`
|
|
39
|
+
);
|
|
38
40
|
break;
|
|
39
41
|
}
|
|
40
42
|
level++;
|
|
@@ -57,10 +59,7 @@ export function getTaskLevel(tasks: Task[], taskId: string): number {
|
|
|
57
59
|
* @param parentId - ID of the proposed parent task
|
|
58
60
|
* @returns true if a child can be added, false if it would exceed max depth
|
|
59
61
|
*/
|
|
60
|
-
export function validateHierarchyDepth(
|
|
61
|
-
tasks: Task[],
|
|
62
|
-
parentId: string
|
|
63
|
-
): boolean {
|
|
62
|
+
export function validateHierarchyDepth(tasks: Task[], parentId: string): boolean {
|
|
64
63
|
const parentLevel = getTaskLevel(tasks, parentId);
|
|
65
64
|
|
|
66
65
|
if (parentLevel === -1) {
|
package/src/utils/index.ts
CHANGED
|
@@ -31,7 +31,13 @@ export {
|
|
|
31
31
|
type DateParseResult,
|
|
32
32
|
type IsWithinDaysResult,
|
|
33
33
|
} from "./date.js";
|
|
34
|
-
export {
|
|
34
|
+
export {
|
|
35
|
+
parseTaskInput,
|
|
36
|
+
parseInboxInput,
|
|
37
|
+
parseInput,
|
|
38
|
+
type ParseTarget,
|
|
39
|
+
type ParsedInput,
|
|
40
|
+
} from "./natural-language.js";
|
|
35
41
|
export {
|
|
36
42
|
MAX_HIERARCHY_DEPTH,
|
|
37
43
|
getTaskLevel,
|
|
@@ -46,11 +52,11 @@ export {
|
|
|
46
52
|
|
|
47
53
|
// Projection utilities (token optimization)
|
|
48
54
|
export {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
formatTask,
|
|
56
|
+
formatTasks,
|
|
57
|
+
formatTasksPaginated,
|
|
58
|
+
formatInboxItem,
|
|
59
|
+
formatInboxItems,
|
|
54
60
|
formatResponse,
|
|
55
61
|
applyPagination,
|
|
56
62
|
truncate,
|