@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.
- package/dist/schemas/task.d.ts +290 -34
- package/dist/schemas/task.d.ts.map +1 -1
- package/dist/schemas/task.js +50 -13
- package/dist/schemas/task.js.map +1 -1
- package/dist/utils/date.d.ts +13 -0
- package/dist/utils/date.d.ts.map +1 -1
- package/dist/utils/date.js +29 -0
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/hierarchy.d.ts +27 -0
- package/dist/utils/hierarchy.d.ts.map +1 -1
- package/dist/utils/hierarchy.js +48 -1
- package/dist/utils/hierarchy.js.map +1 -1
- package/dist/utils/hierarchy.test.js +86 -1
- package/dist/utils/hierarchy.test.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/natural-language.d.ts.map +1 -1
- package/dist/utils/natural-language.js +6 -0
- package/dist/utils/natural-language.js.map +1 -1
- package/dist/utils/natural-language.test.js +42 -1
- package/dist/utils/natural-language.test.js.map +1 -1
- package/dist/utils/priority-queue.test.d.ts +2 -0
- package/dist/utils/priority-queue.test.d.ts.map +1 -0
- package/dist/utils/priority-queue.test.js +82 -0
- package/dist/utils/priority-queue.test.js.map +1 -0
- package/dist/utils/projection.d.ts.map +1 -1
- package/dist/utils/projection.js +13 -3
- package/dist/utils/projection.js.map +1 -1
- package/dist/utils/workspace.test.d.ts +2 -0
- package/dist/utils/workspace.test.d.ts.map +1 -0
- package/dist/utils/workspace.test.js +97 -0
- package/dist/utils/workspace.test.js.map +1 -0
- package/package.json +1 -1
- package/src/schemas/task.ts +60 -14
- package/src/utils/date.ts +40 -0
- package/src/utils/hierarchy.test.ts +94 -0
- package/src/utils/hierarchy.ts +63 -1
- package/src/utils/index.ts +2 -0
- package/src/utils/natural-language.test.ts +61 -1
- package/src/utils/natural-language.ts +12 -0
- package/src/utils/priority-queue.test.ts +103 -0
- package/src/utils/projection.ts +14 -3
- 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;
|
|
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"}
|
package/dist/utils/projection.js
CHANGED
|
@@ -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":"AAAA;;;;;GAKG;AAaH;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAU,EAAE,MAAsB;IAC5D,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,SAAS;YACZ,OAAO;gBACL,EAAE,EAAE,IAAI,CAAC,EAAE;gBACX,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;aACxB,CAAC;QAEJ,KAAK,UAAU;YACb,OAAO;gBACL,EAAE,EAAE,IAAI,CAAC,EAAE;gBACX,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;aACxB,CAAC;QAEJ,KAAK,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"projection.js","sourceRoot":"","sources":["../../src/utils/projection.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
package/src/schemas/task.ts
CHANGED
|
@@ -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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
});
|
package/src/utils/hierarchy.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -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 {
|