@task-mcp/shared 1.0.19 → 1.0.20

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.
Files changed (45) hide show
  1. package/dist/schemas/task.d.ts +290 -34
  2. package/dist/schemas/task.d.ts.map +1 -1
  3. package/dist/schemas/task.js +50 -13
  4. package/dist/schemas/task.js.map +1 -1
  5. package/dist/utils/date.d.ts +13 -0
  6. package/dist/utils/date.d.ts.map +1 -1
  7. package/dist/utils/date.js +29 -0
  8. package/dist/utils/date.js.map +1 -1
  9. package/dist/utils/hierarchy.d.ts +27 -0
  10. package/dist/utils/hierarchy.d.ts.map +1 -1
  11. package/dist/utils/hierarchy.js +48 -1
  12. package/dist/utils/hierarchy.js.map +1 -1
  13. package/dist/utils/hierarchy.test.js +86 -1
  14. package/dist/utils/hierarchy.test.js.map +1 -1
  15. package/dist/utils/index.d.ts +1 -1
  16. package/dist/utils/index.d.ts.map +1 -1
  17. package/dist/utils/index.js +1 -1
  18. package/dist/utils/index.js.map +1 -1
  19. package/dist/utils/natural-language.d.ts.map +1 -1
  20. package/dist/utils/natural-language.js +6 -0
  21. package/dist/utils/natural-language.js.map +1 -1
  22. package/dist/utils/natural-language.test.js +42 -1
  23. package/dist/utils/natural-language.test.js.map +1 -1
  24. package/dist/utils/priority-queue.test.d.ts +2 -0
  25. package/dist/utils/priority-queue.test.d.ts.map +1 -0
  26. package/dist/utils/priority-queue.test.js +82 -0
  27. package/dist/utils/priority-queue.test.js.map +1 -0
  28. package/dist/utils/projection.d.ts.map +1 -1
  29. package/dist/utils/projection.js +13 -3
  30. package/dist/utils/projection.js.map +1 -1
  31. package/dist/utils/workspace.test.d.ts +2 -0
  32. package/dist/utils/workspace.test.d.ts.map +1 -0
  33. package/dist/utils/workspace.test.js +97 -0
  34. package/dist/utils/workspace.test.js.map +1 -0
  35. package/package.json +1 -1
  36. package/src/schemas/task.ts +60 -14
  37. package/src/utils/date.ts +40 -0
  38. package/src/utils/hierarchy.test.ts +94 -0
  39. package/src/utils/hierarchy.ts +63 -1
  40. package/src/utils/index.ts +2 -0
  41. package/src/utils/natural-language.test.ts +61 -1
  42. package/src/utils/natural-language.ts +12 -0
  43. package/src/utils/priority-queue.test.ts +103 -0
  44. package/src/utils/projection.ts +14 -3
  45. package/src/utils/workspace.test.ts +125 -0
@@ -0,0 +1,82 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { PriorityQueue } from "./priority-queue";
3
+ describe("PriorityQueue", () => {
4
+ test("creates empty queue", () => {
5
+ const pq = new PriorityQueue((a, b) => a - b);
6
+ expect(pq.length).toBe(0);
7
+ expect(pq.isEmpty()).toBe(true);
8
+ });
9
+ test("pop from empty queue returns undefined", () => {
10
+ const pq = new PriorityQueue((a, b) => a - b);
11
+ expect(pq.pop()).toBeUndefined();
12
+ });
13
+ test("peek from empty queue returns undefined", () => {
14
+ const pq = new PriorityQueue((a, b) => a - b);
15
+ expect(pq.peek()).toBeUndefined();
16
+ });
17
+ test("single element operations", () => {
18
+ const pq = new PriorityQueue((a, b) => a - b);
19
+ pq.push(42);
20
+ expect(pq.length).toBe(1);
21
+ expect(pq.peek()).toBe(42);
22
+ expect(pq.pop()).toBe(42);
23
+ expect(pq.isEmpty()).toBe(true);
24
+ });
25
+ test("maintains max-heap property (higher value first with a-b comparator)", () => {
26
+ const pq = new PriorityQueue((a, b) => a - b);
27
+ pq.push(5);
28
+ pq.push(2);
29
+ pq.push(8);
30
+ pq.push(1);
31
+ pq.push(9);
32
+ // Max-heap: higher values come first
33
+ expect(pq.pop()).toBe(9);
34
+ expect(pq.pop()).toBe(8);
35
+ expect(pq.pop()).toBe(5);
36
+ expect(pq.pop()).toBe(2);
37
+ expect(pq.pop()).toBe(1);
38
+ });
39
+ test("works with reversed comparator (min-heap behavior)", () => {
40
+ const pq = new PriorityQueue((a, b) => b - a);
41
+ pq.push(5);
42
+ pq.push(2);
43
+ pq.push(8);
44
+ // Reversed comparator: smaller values come first
45
+ expect(pq.pop()).toBe(2);
46
+ expect(pq.pop()).toBe(5);
47
+ expect(pq.pop()).toBe(8);
48
+ });
49
+ test("works with objects", () => {
50
+ // Higher priority value = higher priority (max-heap)
51
+ const pq = new PriorityQueue((a, b) => a.priority - b.priority);
52
+ pq.push({ priority: 3, name: "high" });
53
+ pq.push({ priority: 1, name: "low" });
54
+ pq.push({ priority: 2, name: "medium" });
55
+ expect(pq.pop()?.name).toBe("high");
56
+ expect(pq.pop()?.name).toBe("medium");
57
+ expect(pq.pop()?.name).toBe("low");
58
+ });
59
+ test("length property tracks queue size correctly", () => {
60
+ const pq = new PriorityQueue((a, b) => a - b);
61
+ expect(pq.length).toBe(0);
62
+ pq.push(1);
63
+ expect(pq.length).toBe(1);
64
+ pq.push(2);
65
+ pq.push(3);
66
+ expect(pq.length).toBe(3);
67
+ pq.pop();
68
+ expect(pq.length).toBe(2);
69
+ pq.pop();
70
+ pq.pop();
71
+ expect(pq.length).toBe(0);
72
+ });
73
+ test("peek does not remove element", () => {
74
+ const pq = new PriorityQueue((a, b) => a - b);
75
+ pq.push(5);
76
+ pq.push(10);
77
+ expect(pq.peek()).toBe(10);
78
+ expect(pq.peek()).toBe(10);
79
+ expect(pq.length).toBe(2);
80
+ });
81
+ });
82
+ //# sourceMappingURL=priority-queue.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"priority-queue.test.js","sourceRoot":"","sources":["../../src/utils/priority-queue.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/utils/projection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EACV,cAAc,EACd,WAAW,EACX,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,iBAAiB,EAClB,MAAM,+BAA+B,CAAC;AAEvC;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,GAAG,WAAW,GAAG,WAAW,GAAG,IAAI,CA0BhG;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,IAAI,EAAE,EACb,MAAM,EAAE,cAAc,EACtB,KAAK,CAAC,EAAE,MAAM,GACb,CAAC,WAAW,GAAG,WAAW,GAAG,IAAI,CAAC,EAAE,CAGtC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,IAAI,EAAE,EACb,MAAM,EAAE,cAAc,EACtB,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,GACjB,iBAAiB,CAAC,WAAW,GAAG,WAAW,GAAG,IAAI,CAAC,CAYrD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,cAAc,GACrB,YAAY,GAAG,YAAY,GAAG,SAAS,CAsBzC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,EAAE,EAClB,MAAM,EAAE,cAAc,EACtB,KAAK,CAAC,EAAE,MAAM,GACb,CAAC,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC,EAAE,CAG7C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EACb,MAAM,EAAE,cAAc,EACtB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,MAAM,GAC5C,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,KAAK,EAAE,CAAC,EAAE,EACV,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,GACjB;IAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAUjD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,GAAE,MAAY,GAAG,MAAM,CAGrE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EAAE,EACV,UAAU,EAAE,MAAM,YAAI,EACtB,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GACzB;IAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,CAS/D;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,IAAI,EAAE,EACb,aAAa,GAAE,WAAW,GAAG,UAAU,GAAG,SAAuB,GAChE,IAAI,EAAE,CAiCR"}
1
+ {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/utils/projection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EACV,cAAc,EACd,WAAW,EACX,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,iBAAiB,EAClB,MAAM,+BAA+B,CAAC;AAUvC;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,GAAG,WAAW,GAAG,WAAW,GAAG,IAAI,CA2BhG;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,IAAI,EAAE,EACb,MAAM,EAAE,cAAc,EACtB,KAAK,CAAC,EAAE,MAAM,GACb,CAAC,WAAW,GAAG,WAAW,GAAG,IAAI,CAAC,EAAE,CAGtC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,IAAI,EAAE,EACb,MAAM,EAAE,cAAc,EACtB,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,GACjB,iBAAiB,CAAC,WAAW,GAAG,WAAW,GAAG,IAAI,CAAC,CAYrD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,cAAc,GACrB,YAAY,GAAG,YAAY,GAAG,SAAS,CAuBzC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,EAAE,EAClB,MAAM,EAAE,cAAc,EACtB,KAAK,CAAC,EAAE,MAAM,GACb,CAAC,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC,EAAE,CAG7C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EACb,MAAM,EAAE,cAAc,EACtB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,MAAM,GAC5C,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,KAAK,EAAE,CAAC,EAAE,EACV,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,GACjB;IAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAUjD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,GAAE,MAAY,GAAG,MAAM,CAGrE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EAAE,EACV,UAAU,EAAE,MAAM,YAAI,EACtB,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GACzB;IAAE,SAAS,EAAE,CAAC,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,CAS/D;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,IAAI,EAAE,EACb,aAAa,GAAE,WAAW,GAAG,UAAU,GAAG,SAAuB,GAChE,IAAI,EAAE,CAkCR"}
@@ -4,6 +4,13 @@
4
4
  * Transform full objects into token-efficient projections.
5
5
  * Reduces token usage by 70-88% for list operations.
6
6
  */
7
+ /**
8
+ * Exhaustiveness check helper for switch statements.
9
+ * TypeScript will error if a case is not handled.
10
+ */
11
+ function assertNever(value) {
12
+ throw new Error(`Unexpected value: ${value}`);
13
+ }
7
14
  /**
8
15
  * Project a single task to the specified format
9
16
  */
@@ -28,8 +35,9 @@ export function projectTask(task, format) {
28
35
  parentId: task.parentId,
29
36
  };
30
37
  case "detailed":
31
- default:
32
38
  return task;
39
+ default:
40
+ return assertNever(format);
33
41
  }
34
42
  }
35
43
  /**
@@ -74,8 +82,9 @@ export function projectInboxItem(item, format) {
74
82
  tags: item.tags,
75
83
  };
76
84
  case "detailed":
77
- default:
78
85
  return item;
86
+ default:
87
+ return assertNever(format);
79
88
  }
80
89
  }
81
90
  /**
@@ -162,8 +171,9 @@ export function sortTasks(tasks, secondarySort = "updatedAt") {
162
171
  return dateA - dateB;
163
172
  }
164
173
  case "updatedAt":
165
- default:
166
174
  return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
175
+ default:
176
+ return assertNever(secondarySort);
167
177
  }
168
178
  });
169
179
  }
@@ -1 +1 @@
1
- {"version":3,"file":"projection.js","sourceRoot":"","sources":["../../src/utils/projection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"projection.js","sourceRoot":"","sources":["../../src/utils/projection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=workspace.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.test.d.ts","sourceRoot":"","sources":["../../src/utils/workspace.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,97 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { normalizeWorkspace, detectWorkspaceSync, detectWorkspace, getWorkspaceFromPath, getGitRepoRoot, getGitRepoRootSync, } from "./workspace";
3
+ describe("normalizeWorkspace", () => {
4
+ test("converts to lowercase", () => {
5
+ expect(normalizeWorkspace("MyProject")).toBe("myproject");
6
+ });
7
+ test("replaces spaces with hyphens", () => {
8
+ expect(normalizeWorkspace("my project")).toBe("my-project");
9
+ });
10
+ test("removes special characters", () => {
11
+ expect(normalizeWorkspace("my@project!")).toBe("myproject");
12
+ });
13
+ test("handles empty string", () => {
14
+ expect(normalizeWorkspace("")).toBe("default");
15
+ });
16
+ test("keeps hyphens and underscores", () => {
17
+ expect(normalizeWorkspace("my-project_name")).toBe("my-project_name");
18
+ });
19
+ test("trims leading and trailing hyphens", () => {
20
+ expect(normalizeWorkspace("--my-project--")).toBe("my-project");
21
+ });
22
+ test("handles multiple spaces", () => {
23
+ // Multiple spaces become multiple hyphens, then cleaned by special char removal
24
+ expect(normalizeWorkspace("my project")).toBe("my-project");
25
+ });
26
+ test("handles mixed case with special chars", () => {
27
+ expect(normalizeWorkspace("My@Project#Name")).toBe("myprojectname");
28
+ });
29
+ });
30
+ describe("getWorkspaceFromPath", () => {
31
+ test("extracts workspace from path", () => {
32
+ expect(getWorkspaceFromPath("/home/user/projects/my-app")).toBe("my-app");
33
+ });
34
+ test("handles path with .tasks suffix", () => {
35
+ expect(getWorkspaceFromPath("/projects/my-app/.tasks")).toBe("my-app");
36
+ });
37
+ test("handles path with trailing slash", () => {
38
+ expect(getWorkspaceFromPath("/projects/my-app/.tasks/")).toBe("my-app");
39
+ });
40
+ test("normalizes the extracted name", () => {
41
+ expect(getWorkspaceFromPath("/projects/My App")).toBe("my-app");
42
+ });
43
+ });
44
+ describe("getGitRepoRootSync", () => {
45
+ test("returns string or null for current directory", () => {
46
+ const result = getGitRepoRootSync();
47
+ expect(result === null || typeof result === "string").toBe(true);
48
+ });
49
+ test("returns null for non-git directory", () => {
50
+ const result = getGitRepoRootSync("/tmp");
51
+ expect(result).toBeNull();
52
+ });
53
+ });
54
+ describe("getGitRepoRoot", () => {
55
+ test("returns string or null for current directory", async () => {
56
+ const result = await getGitRepoRoot();
57
+ expect(result === null || typeof result === "string").toBe(true);
58
+ });
59
+ test("returns null for non-git directory", async () => {
60
+ const result = await getGitRepoRoot("/tmp");
61
+ expect(result).toBeNull();
62
+ });
63
+ });
64
+ describe("detectWorkspaceSync", () => {
65
+ test("returns a workspace name", () => {
66
+ const workspace = detectWorkspaceSync();
67
+ expect(typeof workspace).toBe("string");
68
+ expect(workspace.length).toBeGreaterThan(0);
69
+ });
70
+ test("uses tasksDir path when provided and no git repo", () => {
71
+ // This tests the fallback to tasksDir when in a non-git directory
72
+ const workspace = detectWorkspaceSync("/some/path/my-project/.tasks");
73
+ expect(typeof workspace).toBe("string");
74
+ });
75
+ test("returns normalized workspace name", () => {
76
+ const workspace = detectWorkspaceSync();
77
+ // Should be lowercase with no special characters except - and _
78
+ expect(workspace).toMatch(/^[a-z0-9_-]+$/);
79
+ });
80
+ });
81
+ describe("detectWorkspace", () => {
82
+ test("returns a workspace name asynchronously", async () => {
83
+ const workspace = await detectWorkspace();
84
+ expect(typeof workspace).toBe("string");
85
+ expect(workspace.length).toBeGreaterThan(0);
86
+ });
87
+ test("uses tasksDir path when provided", async () => {
88
+ const workspace = await detectWorkspace("/some/path/test-project/.tasks");
89
+ expect(typeof workspace).toBe("string");
90
+ });
91
+ test("returns normalized workspace name", async () => {
92
+ const workspace = await detectWorkspace();
93
+ // Should be lowercase with no special characters except - and _
94
+ expect(workspace).toMatch(/^[a-z0-9_-]+$/);
95
+ });
96
+ });
97
+ //# sourceMappingURL=workspace.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.test.js","sourceRoot":"","sources":["../../src/utils/workspace.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@task-mcp/shared",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
4
4
  "description": "Shared utilities for task-mcp: types, algorithms, and natural language parsing",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -26,22 +26,68 @@ export const Dependency = z.object({
26
26
  });
27
27
  export type Dependency = z.infer<typeof Dependency>;
28
28
 
29
- // Time estimation
30
- export const TimeEstimate = z.object({
31
- optimistic: z.number().optional(), // minutes
32
- expected: z.number().optional(), // minutes
33
- pessimistic: z.number().optional(), // minutes
34
- confidence: z.enum(["low", "medium", "high"]).optional(),
35
- });
29
+ // Time estimation (values in minutes, max 10080 = 1 week)
30
+ export const TimeEstimate = z
31
+ .object({
32
+ optimistic: z.number().min(0).max(10080).optional(), // minutes
33
+ expected: z.number().min(0).max(10080).optional(), // minutes
34
+ pessimistic: z.number().min(0).max(10080).optional(), // minutes
35
+ confidence: z.enum(["low", "medium", "high"]).optional(),
36
+ })
37
+ .refine(
38
+ (data) => {
39
+ const { optimistic, expected, pessimistic } = data;
40
+ if (
41
+ optimistic !== undefined &&
42
+ expected !== undefined &&
43
+ optimistic > expected
44
+ ) {
45
+ return false;
46
+ }
47
+ if (
48
+ expected !== undefined &&
49
+ pessimistic !== undefined &&
50
+ expected > pessimistic
51
+ ) {
52
+ return false;
53
+ }
54
+ if (
55
+ optimistic !== undefined &&
56
+ pessimistic !== undefined &&
57
+ optimistic > pessimistic
58
+ ) {
59
+ return false;
60
+ }
61
+ return true;
62
+ },
63
+ { message: "Time estimates must satisfy: optimistic <= expected <= pessimistic" }
64
+ );
36
65
  export type TimeEstimate = z.infer<typeof TimeEstimate>;
37
66
 
38
- // Recurrence pattern
39
- export const Recurrence = z.object({
40
- pattern: z.enum(["daily", "weekly", "monthly", "after_completion"]),
41
- interval: z.number().optional(), // every N days/weeks/months
42
- daysOfWeek: z.array(z.number()).optional(), // 0-6 for weekly
43
- endDate: z.string().optional(),
44
- });
67
+ // Recurrence pattern (discriminated union for type-safe pattern handling)
68
+ export const Recurrence = z.discriminatedUnion("pattern", [
69
+ z.object({
70
+ pattern: z.literal("daily"),
71
+ interval: z.number().min(1).optional(), // every N days
72
+ endDate: z.string().optional(),
73
+ }),
74
+ z.object({
75
+ pattern: z.literal("weekly"),
76
+ interval: z.number().min(1).optional(), // every N weeks
77
+ daysOfWeek: z.array(z.number().min(0).max(6)).optional(), // 0-6 for weekly
78
+ endDate: z.string().optional(),
79
+ }),
80
+ z.object({
81
+ pattern: z.literal("monthly"),
82
+ interval: z.number().min(1).optional(), // every N months
83
+ endDate: z.string().optional(),
84
+ }),
85
+ z.object({
86
+ pattern: z.literal("after_completion"),
87
+ interval: z.number().min(1), // Required: days after completion
88
+ endDate: z.string().optional(),
89
+ }),
90
+ ]);
45
91
  export type Recurrence = z.infer<typeof Recurrence>;
46
92
 
47
93
  // Complexity factors that contribute to task difficulty
package/src/utils/date.ts CHANGED
@@ -28,6 +28,15 @@ export type DateParseResult =
28
28
  | { success: true; date: Date }
29
29
  | { success: false; error: string; reason: DateParseErrorReason };
30
30
 
31
+ /**
32
+ * Result type for isWithinDaysSafe operation
33
+ */
34
+ export interface IsWithinDaysResult {
35
+ success: boolean;
36
+ withinDays?: boolean;
37
+ error?: string;
38
+ }
39
+
31
40
  /**
32
41
  * Get current ISO timestamp
33
42
  */
@@ -351,3 +360,34 @@ export function isWithinDays(date: Date | string, days: number): boolean {
351
360
  future.setDate(future.getDate() + days);
352
361
  return d >= today && d <= future;
353
362
  }
363
+
364
+ /**
365
+ * Safe version of isWithinDays that returns a Result type
366
+ * Allows distinguishing between errors and actual out-of-range results
367
+ */
368
+ export function isWithinDaysSafe(date: Date | string, days: number): IsWithinDaysResult {
369
+ if (typeof days !== "number" || !Number.isFinite(days) || days < 0) {
370
+ return { success: false, error: `Invalid days parameter: ${days}` };
371
+ }
372
+
373
+ let targetDate: Date;
374
+ if (typeof date === "string") {
375
+ targetDate = new Date(date);
376
+ if (isNaN(targetDate.getTime())) {
377
+ return { success: false, error: `Invalid date string: ${date}` };
378
+ }
379
+ } else if (date instanceof Date) {
380
+ if (isNaN(date.getTime())) {
381
+ return { success: false, error: "Invalid Date object" };
382
+ }
383
+ targetDate = date;
384
+ } else {
385
+ return { success: false, error: `Invalid date type: ${typeof date}` };
386
+ }
387
+
388
+ const now = new Date();
389
+ const diffMs = targetDate.getTime() - now.getTime();
390
+ const diffDays = diffMs / (1000 * 60 * 60 * 24);
391
+
392
+ return { success: true, withinDays: diffDays >= 0 && diffDays <= days };
393
+ }
@@ -8,6 +8,7 @@ import {
8
8
  getChildTasks,
9
9
  getRootTask,
10
10
  buildTaskTree,
11
+ validateHierarchy,
11
12
  } from "./hierarchy.js";
12
13
  import type { Task } from "../schemas/task.js";
13
14
 
@@ -409,3 +410,96 @@ describe("buildTaskTree", () => {
409
410
  expect(tree[0]!.children[0]!.task.id).toBe("B");
410
411
  });
411
412
  });
413
+
414
+ describe("validateHierarchy", () => {
415
+ test("returns valid for empty task list", () => {
416
+ const result = validateHierarchy([]);
417
+ expect(result.valid).toBe(true);
418
+ expect(result.orphanedReferences).toEqual([]);
419
+ expect(result.circularReferences).toEqual([]);
420
+ expect(result.maxDepthExceeded).toEqual([]);
421
+ });
422
+
423
+ test("returns valid for healthy hierarchy", () => {
424
+ const tasks = [
425
+ createTask("A"),
426
+ createTask("B", "A"),
427
+ createTask("C", "B"),
428
+ ];
429
+ const result = validateHierarchy(tasks);
430
+ expect(result.valid).toBe(true);
431
+ expect(result.orphanedReferences).toEqual([]);
432
+ expect(result.circularReferences).toEqual([]);
433
+ expect(result.maxDepthExceeded).toEqual([]);
434
+ });
435
+
436
+ test("detects orphaned parent references", () => {
437
+ const tasks = [
438
+ createTask("A"),
439
+ createTask("B", "nonexistent"),
440
+ ];
441
+ const result = validateHierarchy(tasks);
442
+ expect(result.valid).toBe(false);
443
+ expect(result.orphanedReferences).toEqual([
444
+ { taskId: "B", parentId: "nonexistent" },
445
+ ]);
446
+ });
447
+
448
+ test("detects multiple orphaned references", () => {
449
+ const tasks = [
450
+ createTask("A"),
451
+ createTask("B", "missing1"),
452
+ createTask("C", "missing2"),
453
+ ];
454
+ const result = validateHierarchy(tasks);
455
+ expect(result.valid).toBe(false);
456
+ expect(result.orphanedReferences.length).toBe(2);
457
+ expect(result.orphanedReferences).toContainEqual({ taskId: "B", parentId: "missing1" });
458
+ expect(result.orphanedReferences).toContainEqual({ taskId: "C", parentId: "missing2" });
459
+ });
460
+
461
+ test("detects circular references (self-reference)", () => {
462
+ const tasks = [createTask("A", "A")];
463
+ const result = validateHierarchy(tasks);
464
+ expect(result.valid).toBe(false);
465
+ expect(result.circularReferences).toContain("A");
466
+ });
467
+
468
+ test("detects circular references (A -> B -> A)", () => {
469
+ const taskA = createTask("A", "B");
470
+ const taskB = createTask("B", "A");
471
+ const tasks = [taskA, taskB];
472
+ const result = validateHierarchy(tasks);
473
+ expect(result.valid).toBe(false);
474
+ expect(result.circularReferences.length).toBeGreaterThan(0);
475
+ });
476
+
477
+ test("detects max depth exceeded", () => {
478
+ // Create chain: A -> B -> C -> D -> E (E at level 4, exceeds MAX_HIERARCHY_DEPTH of 3)
479
+ const tasks = [
480
+ createTask("A"),
481
+ createTask("B", "A"),
482
+ createTask("C", "B"),
483
+ createTask("D", "C"),
484
+ createTask("E", "D"),
485
+ ];
486
+ const result = validateHierarchy(tasks);
487
+ expect(result.valid).toBe(false);
488
+ expect(result.maxDepthExceeded).toContainEqual({ taskId: "E", depth: 4 });
489
+ });
490
+
491
+ test("returns all issues when multiple problems exist", () => {
492
+ const tasks = [
493
+ createTask("A"),
494
+ createTask("B", "A"),
495
+ createTask("C", "B"),
496
+ createTask("D", "C"),
497
+ createTask("E", "D"), // Level 4, exceeds max
498
+ createTask("F", "nonexistent"), // Orphaned
499
+ ];
500
+ const result = validateHierarchy(tasks);
501
+ expect(result.valid).toBe(false);
502
+ expect(result.orphanedReferences.length).toBeGreaterThan(0);
503
+ expect(result.maxDepthExceeded.length).toBeGreaterThan(0);
504
+ });
505
+ });
@@ -34,7 +34,8 @@ 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
- break; // Parent not found, stop traversal
37
+ console.warn(`[task-mcp] Orphaned parent reference: task "${currentTask.id}" references non-existent parent "${currentTask.parentId}"`);
38
+ break;
38
39
  }
39
40
  level++;
40
41
  currentTask = parent;
@@ -239,3 +240,64 @@ export function buildTaskTree(
239
240
  const rootTasks = tasks.filter((t) => !t.parentId);
240
241
  return rootTasks.map((task) => buildNode(task, 0, new Set()));
241
242
  }
243
+
244
+ /**
245
+ * Result of hierarchy validation.
246
+ */
247
+ export interface HierarchyValidationResult {
248
+ valid: boolean;
249
+ orphanedReferences: { taskId: string; parentId: string }[];
250
+ circularReferences: string[];
251
+ maxDepthExceeded: { taskId: string; depth: number }[];
252
+ }
253
+
254
+ /**
255
+ * Validates the hierarchy integrity of a task list.
256
+ *
257
+ * Checks for:
258
+ * - Orphaned parent references (task references non-existent parent)
259
+ * - Circular references (task is its own ancestor)
260
+ * - Tasks exceeding maximum hierarchy depth
261
+ *
262
+ * @param tasks - Array of all tasks to validate
263
+ * @returns Validation result with details of any issues found
264
+ */
265
+ export function validateHierarchy(tasks: Task[]): HierarchyValidationResult {
266
+ const taskMap = new Map(tasks.map((t) => [t.id, t]));
267
+ const result: HierarchyValidationResult = {
268
+ valid: true,
269
+ orphanedReferences: [],
270
+ circularReferences: [],
271
+ maxDepthExceeded: [],
272
+ };
273
+
274
+ for (const task of tasks) {
275
+ // Check for orphaned parent references
276
+ if (task.parentId && !taskMap.has(task.parentId)) {
277
+ result.orphanedReferences.push({ taskId: task.id, parentId: task.parentId });
278
+ result.valid = false;
279
+ }
280
+
281
+ // Check for circular references
282
+ const visited = new Set<string>();
283
+ let current: Task | undefined = task;
284
+ while (current?.parentId) {
285
+ if (visited.has(current.id)) {
286
+ result.circularReferences.push(task.id);
287
+ result.valid = false;
288
+ break;
289
+ }
290
+ visited.add(current.id);
291
+ current = taskMap.get(current.parentId);
292
+ }
293
+
294
+ // Check for max depth exceeded
295
+ const level = getTaskLevel(tasks, task.id);
296
+ if (level > MAX_HIERARCHY_DEPTH) {
297
+ result.maxDepthExceeded.push({ taskId: task.id, depth: level });
298
+ result.valid = false;
299
+ }
300
+ }
301
+
302
+ return result;
303
+ }
@@ -24,10 +24,12 @@ export {
24
24
  isToday,
25
25
  isPastDue,
26
26
  isWithinDays,
27
+ isWithinDaysSafe,
27
28
  isValidDate,
28
29
  DateParseError,
29
30
  type DateParseErrorReason,
30
31
  type DateParseResult,
32
+ type IsWithinDaysResult,
31
33
  } from "./date.js";
32
34
  export { parseTaskInput, parseInboxInput, parseInput, type ParseTarget, type ParsedInput } from "./natural-language.js";
33
35
  export {