@task-mcp/shared 1.0.19 → 1.0.21
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/package.json +1 -6
- package/src/schemas/task.ts +60 -14
- package/src/utils/date.ts +40 -0
- package/src/utils/hierarchy.ts +63 -1
- package/src/utils/index.ts +2 -0
- package/src/utils/natural-language.ts +12 -0
- package/src/utils/projection.ts +14 -3
- package/dist/algorithms/critical-path.d.ts +0 -46
- package/dist/algorithms/critical-path.d.ts.map +0 -1
- package/dist/algorithms/critical-path.js +0 -320
- package/dist/algorithms/critical-path.js.map +0 -1
- package/dist/algorithms/critical-path.test.d.ts +0 -2
- package/dist/algorithms/critical-path.test.d.ts.map +0 -1
- package/dist/algorithms/critical-path.test.js +0 -194
- package/dist/algorithms/critical-path.test.js.map +0 -1
- package/dist/algorithms/dependency-integrity.d.ts +0 -81
- package/dist/algorithms/dependency-integrity.d.ts.map +0 -1
- package/dist/algorithms/dependency-integrity.js +0 -207
- package/dist/algorithms/dependency-integrity.js.map +0 -1
- package/dist/algorithms/dependency-integrity.test.d.ts +0 -2
- package/dist/algorithms/dependency-integrity.test.d.ts.map +0 -1
- package/dist/algorithms/dependency-integrity.test.js +0 -309
- package/dist/algorithms/dependency-integrity.test.js.map +0 -1
- package/dist/algorithms/index.d.ts +0 -5
- package/dist/algorithms/index.d.ts.map +0 -1
- package/dist/algorithms/index.js +0 -5
- package/dist/algorithms/index.js.map +0 -1
- package/dist/algorithms/tech-analysis.d.ts +0 -106
- package/dist/algorithms/tech-analysis.d.ts.map +0 -1
- package/dist/algorithms/tech-analysis.js +0 -344
- package/dist/algorithms/tech-analysis.js.map +0 -1
- package/dist/algorithms/tech-analysis.test.d.ts +0 -2
- package/dist/algorithms/tech-analysis.test.d.ts.map +0 -1
- package/dist/algorithms/tech-analysis.test.js +0 -338
- package/dist/algorithms/tech-analysis.test.js.map +0 -1
- package/dist/algorithms/topological-sort.d.ts +0 -41
- package/dist/algorithms/topological-sort.d.ts.map +0 -1
- package/dist/algorithms/topological-sort.js +0 -165
- package/dist/algorithms/topological-sort.js.map +0 -1
- package/dist/algorithms/topological-sort.test.d.ts +0 -2
- package/dist/algorithms/topological-sort.test.d.ts.map +0 -1
- package/dist/algorithms/topological-sort.test.js +0 -162
- package/dist/algorithms/topological-sort.test.js.map +0 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -7
- package/dist/index.js.map +0 -1
- package/dist/schemas/inbox.d.ts +0 -55
- package/dist/schemas/inbox.d.ts.map +0 -1
- package/dist/schemas/inbox.js +0 -25
- package/dist/schemas/inbox.js.map +0 -1
- package/dist/schemas/index.d.ts +0 -7
- package/dist/schemas/index.d.ts.map +0 -1
- package/dist/schemas/index.js +0 -17
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/project.d.ts +0 -177
- package/dist/schemas/project.d.ts.map +0 -1
- package/dist/schemas/project.js +0 -56
- package/dist/schemas/project.js.map +0 -1
- package/dist/schemas/response-format.d.ts +0 -148
- package/dist/schemas/response-format.d.ts.map +0 -1
- package/dist/schemas/response-format.js +0 -18
- package/dist/schemas/response-format.js.map +0 -1
- package/dist/schemas/response-schema.d.ts +0 -307
- package/dist/schemas/response-schema.d.ts.map +0 -1
- package/dist/schemas/response-schema.js +0 -75
- package/dist/schemas/response-schema.js.map +0 -1
- package/dist/schemas/response-schema.test.d.ts +0 -2
- package/dist/schemas/response-schema.test.d.ts.map +0 -1
- package/dist/schemas/response-schema.test.js +0 -256
- package/dist/schemas/response-schema.test.js.map +0 -1
- package/dist/schemas/state.d.ts +0 -17
- package/dist/schemas/state.d.ts.map +0 -1
- package/dist/schemas/state.js +0 -17
- package/dist/schemas/state.js.map +0 -1
- package/dist/schemas/task.d.ts +0 -625
- package/dist/schemas/task.d.ts.map +0 -1
- package/dist/schemas/task.js +0 -152
- package/dist/schemas/task.js.map +0 -1
- package/dist/schemas/view.d.ts +0 -143
- package/dist/schemas/view.d.ts.map +0 -1
- package/dist/schemas/view.js +0 -48
- package/dist/schemas/view.js.map +0 -1
- package/dist/utils/dashboard-renderer.d.ts +0 -93
- package/dist/utils/dashboard-renderer.d.ts.map +0 -1
- package/dist/utils/dashboard-renderer.js +0 -424
- package/dist/utils/dashboard-renderer.js.map +0 -1
- package/dist/utils/dashboard-renderer.test.d.ts +0 -2
- package/dist/utils/dashboard-renderer.test.d.ts.map +0 -1
- package/dist/utils/dashboard-renderer.test.js +0 -774
- package/dist/utils/dashboard-renderer.test.js.map +0 -1
- package/dist/utils/date.d.ts +0 -81
- package/dist/utils/date.d.ts.map +0 -1
- package/dist/utils/date.js +0 -294
- package/dist/utils/date.js.map +0 -1
- package/dist/utils/date.test.d.ts +0 -2
- package/dist/utils/date.test.d.ts.map +0 -1
- package/dist/utils/date.test.js +0 -276
- package/dist/utils/date.test.js.map +0 -1
- package/dist/utils/hierarchy.d.ts +0 -75
- package/dist/utils/hierarchy.d.ts.map +0 -1
- package/dist/utils/hierarchy.js +0 -189
- package/dist/utils/hierarchy.js.map +0 -1
- package/dist/utils/hierarchy.test.d.ts +0 -2
- package/dist/utils/hierarchy.test.d.ts.map +0 -1
- package/dist/utils/hierarchy.test.js +0 -351
- package/dist/utils/hierarchy.test.js.map +0 -1
- package/dist/utils/id.d.ts +0 -60
- package/dist/utils/id.d.ts.map +0 -1
- package/dist/utils/id.js +0 -118
- package/dist/utils/id.js.map +0 -1
- package/dist/utils/id.test.d.ts +0 -2
- package/dist/utils/id.test.d.ts.map +0 -1
- package/dist/utils/id.test.js +0 -193
- package/dist/utils/id.test.js.map +0 -1
- package/dist/utils/index.d.ts +0 -12
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -34
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/natural-language.d.ts +0 -57
- package/dist/utils/natural-language.d.ts.map +0 -1
- package/dist/utils/natural-language.js +0 -205
- package/dist/utils/natural-language.js.map +0 -1
- package/dist/utils/natural-language.test.d.ts +0 -2
- package/dist/utils/natural-language.test.d.ts.map +0 -1
- package/dist/utils/natural-language.test.js +0 -156
- package/dist/utils/natural-language.test.js.map +0 -1
- package/dist/utils/priority-queue.d.ts +0 -17
- package/dist/utils/priority-queue.d.ts.map +0 -1
- package/dist/utils/priority-queue.js +0 -62
- package/dist/utils/priority-queue.js.map +0 -1
- package/dist/utils/projection.d.ts +0 -65
- package/dist/utils/projection.d.ts.map +0 -1
- package/dist/utils/projection.js +0 -170
- package/dist/utils/projection.js.map +0 -1
- package/dist/utils/projection.test.d.ts +0 -2
- package/dist/utils/projection.test.d.ts.map +0 -1
- package/dist/utils/projection.test.js +0 -336
- package/dist/utils/projection.test.js.map +0 -1
- package/dist/utils/terminal-ui.d.ts +0 -208
- package/dist/utils/terminal-ui.d.ts.map +0 -1
- package/dist/utils/terminal-ui.js +0 -611
- package/dist/utils/terminal-ui.js.map +0 -1
- package/dist/utils/terminal-ui.test.d.ts +0 -2
- package/dist/utils/terminal-ui.test.d.ts.map +0 -1
- package/dist/utils/terminal-ui.test.js +0 -683
- package/dist/utils/terminal-ui.test.js.map +0 -1
- package/dist/utils/workspace.d.ts +0 -100
- package/dist/utils/workspace.d.ts.map +0 -1
- package/dist/utils/workspace.js +0 -173
- package/dist/utils/workspace.js.map +0 -1
- package/src/algorithms/critical-path.test.ts +0 -241
- package/src/algorithms/dependency-integrity.test.ts +0 -348
- package/src/algorithms/tech-analysis.test.ts +0 -413
- package/src/algorithms/topological-sort.test.ts +0 -190
- package/src/schemas/response-schema.test.ts +0 -314
- package/src/utils/dashboard-renderer.test.ts +0 -983
- package/src/utils/date.test.ts +0 -329
- package/src/utils/hierarchy.test.ts +0 -411
- package/src/utils/id.test.ts +0 -235
- package/src/utils/natural-language.test.ts +0 -182
- package/src/utils/projection.test.ts +0 -425
- package/src/utils/terminal-ui.test.ts +0 -831
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import { parseTaskInput } from "./natural-language.js";
|
|
3
|
-
|
|
4
|
-
describe("parseTaskInput", () => {
|
|
5
|
-
describe("priority parsing", () => {
|
|
6
|
-
test("parses !critical priority", () => {
|
|
7
|
-
const result = parseTaskInput("Fix bug !critical");
|
|
8
|
-
expect(result.priority).toBe("critical");
|
|
9
|
-
expect(result.title).toBe("Fix bug");
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test("parses !high priority", () => {
|
|
13
|
-
const result = parseTaskInput("Review PR !high");
|
|
14
|
-
expect(result.priority).toBe("high");
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test("parses !medium priority", () => {
|
|
18
|
-
const result = parseTaskInput("Write docs !medium");
|
|
19
|
-
expect(result.priority).toBe("medium");
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("parses !low priority", () => {
|
|
23
|
-
const result = parseTaskInput("Clean up !low");
|
|
24
|
-
expect(result.priority).toBe("low");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("parses Korean priority !높음", () => {
|
|
28
|
-
const result = parseTaskInput("버그 수정 !높음");
|
|
29
|
-
expect(result.priority).toBe("high");
|
|
30
|
-
expect(result.title).toBe("버그 수정");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("parses Korean priority !긴급", () => {
|
|
34
|
-
const result = parseTaskInput("장애 대응 !긴급");
|
|
35
|
-
expect(result.priority).toBe("critical");
|
|
36
|
-
expect(result.title).toBe("장애 대응");
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe("tag parsing", () => {
|
|
41
|
-
test("parses single tag", () => {
|
|
42
|
-
const result = parseTaskInput("Fix auth #backend");
|
|
43
|
-
expect(result.tags).toEqual(["backend"]);
|
|
44
|
-
expect(result.title).toBe("Fix auth");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("parses multiple tags", () => {
|
|
48
|
-
const result = parseTaskInput("Update API #backend #api #v2");
|
|
49
|
-
expect(result.tags).toEqual(["backend", "api", "v2"]);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("parses Korean tags", () => {
|
|
53
|
-
const result = parseTaskInput("보고서 작성 #업무 #문서");
|
|
54
|
-
expect(result.tags).toEqual(["업무", "문서"]);
|
|
55
|
-
expect(result.title).toBe("보고서 작성");
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe("context parsing", () => {
|
|
60
|
-
test("parses single context", () => {
|
|
61
|
-
const result = parseTaskInput("Deep work task @focus");
|
|
62
|
-
expect(result.contexts).toEqual(["focus"]);
|
|
63
|
-
expect(result.title).toBe("Deep work task");
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("parses multiple contexts", () => {
|
|
67
|
-
const result = parseTaskInput("Review code @focus @office");
|
|
68
|
-
expect(result.contexts).toEqual(["focus", "office"]);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe("due date parsing", () => {
|
|
73
|
-
test("parses 'by tomorrow'", () => {
|
|
74
|
-
const result = parseTaskInput("Submit report by tomorrow");
|
|
75
|
-
expect(result.dueDate).toBeDefined();
|
|
76
|
-
expect(result.title).toBe("Submit report");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("parses 'until Friday'", () => {
|
|
80
|
-
const result = parseTaskInput("Complete feature until Friday");
|
|
81
|
-
expect(result.dueDate).toBeDefined();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("parses Korean '내일까지'", () => {
|
|
85
|
-
const result = parseTaskInput("보고서 제출 내일까지");
|
|
86
|
-
expect(result.dueDate).toBeDefined();
|
|
87
|
-
expect(result.title).toBe("보고서 제출");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("parses standalone 'tomorrow' at end", () => {
|
|
91
|
-
const result = parseTaskInput("Submit PR tomorrow");
|
|
92
|
-
expect(result.dueDate).toBeDefined();
|
|
93
|
-
expect(result.title).toBe("Submit PR");
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe("time estimate parsing", () => {
|
|
98
|
-
test("parses hours only", () => {
|
|
99
|
-
const result = parseTaskInput("Write tests 2h");
|
|
100
|
-
expect(result.estimate?.expected).toBe(120);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("parses minutes only", () => {
|
|
104
|
-
const result = parseTaskInput("Quick fix 30m");
|
|
105
|
-
expect(result.estimate?.expected).toBe(30);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("parses combined hours and minutes", () => {
|
|
109
|
-
const result = parseTaskInput("Big refactor 1h30m");
|
|
110
|
-
expect(result.estimate?.expected).toBe(90);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("removes time estimate from title", () => {
|
|
114
|
-
const result = parseTaskInput("Task 2h30m done");
|
|
115
|
-
expect(result.estimate?.expected).toBe(150);
|
|
116
|
-
expect(result.title).toBe("Task done");
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe("sortOrder parsing", () => {
|
|
121
|
-
test("parses ^1 as sortOrder 1", () => {
|
|
122
|
-
const result = parseTaskInput("First task ^1");
|
|
123
|
-
expect(result.sortOrder).toBe(1);
|
|
124
|
-
expect(result.title).toBe("First task");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("parses ^10 as sortOrder 10", () => {
|
|
128
|
-
const result = parseTaskInput("Tenth task ^10");
|
|
129
|
-
expect(result.sortOrder).toBe(10);
|
|
130
|
-
expect(result.title).toBe("Tenth task");
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test("parses sortOrder in middle of input", () => {
|
|
134
|
-
const result = parseTaskInput("Task ^5 with more text");
|
|
135
|
-
expect(result.sortOrder).toBe(5);
|
|
136
|
-
expect(result.title).toBe("Task with more text");
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test("parses sortOrder with other metadata", () => {
|
|
140
|
-
const result = parseTaskInput("Task ^3 #dev !high");
|
|
141
|
-
expect(result.sortOrder).toBe(3);
|
|
142
|
-
expect(result.tags).toEqual(["dev"]);
|
|
143
|
-
expect(result.priority).toBe("high");
|
|
144
|
-
expect(result.title).toBe("Task");
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
describe("combined parsing", () => {
|
|
149
|
-
test("parses full English input", () => {
|
|
150
|
-
const result = parseTaskInput("Review PR #dev !high @focus");
|
|
151
|
-
expect(result.title).toBe("Review PR");
|
|
152
|
-
expect(result.tags).toEqual(["dev"]);
|
|
153
|
-
expect(result.priority).toBe("high");
|
|
154
|
-
expect(result.contexts).toEqual(["focus"]);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("parses input with due date", () => {
|
|
158
|
-
const result = parseTaskInput("Submit report by tomorrow #work");
|
|
159
|
-
expect(result.title).toBe("Submit report");
|
|
160
|
-
expect(result.tags).toEqual(["work"]);
|
|
161
|
-
expect(result.dueDate).toBeDefined();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("parses full Korean input", () => {
|
|
165
|
-
const result = parseTaskInput("보고서 작성 내일까지 #업무 !높음 @집중");
|
|
166
|
-
expect(result.title).toBe("보고서 작성");
|
|
167
|
-
expect(result.tags).toEqual(["업무"]);
|
|
168
|
-
expect(result.priority).toBe("high");
|
|
169
|
-
expect(result.contexts).toEqual(["집중"]);
|
|
170
|
-
expect(result.dueDate).toBeDefined();
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test("returns clean title with no metadata", () => {
|
|
174
|
-
const result = parseTaskInput("Simple task");
|
|
175
|
-
expect(result.title).toBe("Simple task");
|
|
176
|
-
expect(result.tags).toBeUndefined();
|
|
177
|
-
expect(result.priority).toBeUndefined();
|
|
178
|
-
expect(result.contexts).toBeUndefined();
|
|
179
|
-
expect(result.dueDate).toBeUndefined();
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
});
|
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
projectTask,
|
|
4
|
-
projectTasks,
|
|
5
|
-
projectTasksPaginated,
|
|
6
|
-
projectInboxItem,
|
|
7
|
-
projectInboxItems,
|
|
8
|
-
formatResponse,
|
|
9
|
-
applyPagination,
|
|
10
|
-
truncate,
|
|
11
|
-
summarizeList,
|
|
12
|
-
} from "./projection.js";
|
|
13
|
-
import type { Task } from "../schemas/task.js";
|
|
14
|
-
import type { InboxItem } from "../schemas/inbox.js";
|
|
15
|
-
|
|
16
|
-
// Test fixtures
|
|
17
|
-
const createTask = (overrides: Partial<Task> = {}): Task => {
|
|
18
|
-
const base: Task = {
|
|
19
|
-
id: "task-1",
|
|
20
|
-
workspace: "test-workspace",
|
|
21
|
-
title: "Test Task",
|
|
22
|
-
status: "pending",
|
|
23
|
-
priority: "medium",
|
|
24
|
-
createdAt: "2025-01-01T00:00:00.000Z",
|
|
25
|
-
updatedAt: "2025-01-01T00:00:00.000Z",
|
|
26
|
-
dueDate: "2025-01-15",
|
|
27
|
-
tags: ["test", "unit"],
|
|
28
|
-
contexts: ["work"],
|
|
29
|
-
parentId: "parent-1",
|
|
30
|
-
description: "This is a test task with a long description",
|
|
31
|
-
estimate: { expected: 60, optimistic: 30, pessimistic: 120 },
|
|
32
|
-
dependencies: [{ taskId: "dep-1", type: "blocked_by" }],
|
|
33
|
-
};
|
|
34
|
-
return { ...base, ...overrides };
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const createInboxItem = (overrides: Partial<InboxItem> = {}): InboxItem => ({
|
|
38
|
-
id: "inbox-1",
|
|
39
|
-
content: "Quick idea for later",
|
|
40
|
-
status: "pending",
|
|
41
|
-
capturedAt: "2025-01-01T00:00:00.000Z",
|
|
42
|
-
tags: ["idea"],
|
|
43
|
-
source: "cli",
|
|
44
|
-
...overrides,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe("projectTask", () => {
|
|
48
|
-
test("concise format returns only 4 essential fields", () => {
|
|
49
|
-
const task = createTask();
|
|
50
|
-
const result = projectTask(task, "concise");
|
|
51
|
-
|
|
52
|
-
expect(result).toEqual({
|
|
53
|
-
id: "task-1",
|
|
54
|
-
title: "Test Task",
|
|
55
|
-
status: "pending",
|
|
56
|
-
priority: "medium",
|
|
57
|
-
});
|
|
58
|
-
expect(Object.keys(result)).toHaveLength(4);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("standard format returns 8 common fields", () => {
|
|
62
|
-
const task = createTask();
|
|
63
|
-
const result = projectTask(task, "standard");
|
|
64
|
-
|
|
65
|
-
expect(result).toEqual({
|
|
66
|
-
id: "task-1",
|
|
67
|
-
title: "Test Task",
|
|
68
|
-
status: "pending",
|
|
69
|
-
priority: "medium",
|
|
70
|
-
dueDate: "2025-01-15",
|
|
71
|
-
tags: ["test", "unit"],
|
|
72
|
-
contexts: ["work"],
|
|
73
|
-
parentId: "parent-1",
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("detailed format returns full task", () => {
|
|
78
|
-
const task = createTask();
|
|
79
|
-
const result = projectTask(task, "detailed");
|
|
80
|
-
|
|
81
|
-
expect(result).toBe(task); // Same reference
|
|
82
|
-
expect(result).toHaveProperty("description");
|
|
83
|
-
expect(result).toHaveProperty("estimate");
|
|
84
|
-
expect(result).toHaveProperty("dependencies");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("concise format omits undefined optional fields", () => {
|
|
88
|
-
// Create task without optional fields by deleting them
|
|
89
|
-
const task = createTask();
|
|
90
|
-
delete (task as Record<string, unknown>)["dueDate"];
|
|
91
|
-
delete (task as Record<string, unknown>)["tags"];
|
|
92
|
-
const result = projectTask(task, "concise");
|
|
93
|
-
|
|
94
|
-
expect(result).not.toHaveProperty("dueDate");
|
|
95
|
-
expect(result).not.toHaveProperty("tags");
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe("projectTasks", () => {
|
|
100
|
-
test("projects all tasks without limit", () => {
|
|
101
|
-
const tasks = [createTask({ id: "t1" }), createTask({ id: "t2" }), createTask({ id: "t3" })];
|
|
102
|
-
const result = projectTasks(tasks, "concise");
|
|
103
|
-
|
|
104
|
-
expect(result).toHaveLength(3);
|
|
105
|
-
expect(result[0]).toEqual({ id: "t1", title: "Test Task", status: "pending", priority: "medium" });
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("limits tasks when limit provided", () => {
|
|
109
|
-
const tasks = [
|
|
110
|
-
createTask({ id: "t1" }),
|
|
111
|
-
createTask({ id: "t2" }),
|
|
112
|
-
createTask({ id: "t3" }),
|
|
113
|
-
createTask({ id: "t4" }),
|
|
114
|
-
createTask({ id: "t5" }),
|
|
115
|
-
];
|
|
116
|
-
const result = projectTasks(tasks, "concise", 2);
|
|
117
|
-
|
|
118
|
-
expect(result).toHaveLength(2);
|
|
119
|
-
expect(result.map((t) => t.id)).toEqual(["t1", "t2"]);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("returns all tasks if limit exceeds length", () => {
|
|
123
|
-
const tasks = [createTask({ id: "t1" }), createTask({ id: "t2" })];
|
|
124
|
-
const result = projectTasks(tasks, "concise", 100);
|
|
125
|
-
|
|
126
|
-
expect(result).toHaveLength(2);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("returns empty array for empty input", () => {
|
|
130
|
-
const result = projectTasks([], "concise");
|
|
131
|
-
expect(result).toEqual([]);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
describe("projectTasksPaginated", () => {
|
|
136
|
-
test("returns paginated response with correct metadata", () => {
|
|
137
|
-
const tasks = Array.from({ length: 50 }, (_, i) => createTask({ id: `t${i}` }));
|
|
138
|
-
const result = projectTasksPaginated(tasks, "concise", 10, 0);
|
|
139
|
-
|
|
140
|
-
expect(result.items).toHaveLength(10);
|
|
141
|
-
expect(result.total).toBe(50);
|
|
142
|
-
expect(result.limit).toBe(10);
|
|
143
|
-
expect(result.offset).toBe(0);
|
|
144
|
-
expect(result.hasMore).toBe(true);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("returns correct page with offset", () => {
|
|
148
|
-
const tasks = Array.from({ length: 50 }, (_, i) => createTask({ id: `t${i}` }));
|
|
149
|
-
const result = projectTasksPaginated(tasks, "concise", 10, 20);
|
|
150
|
-
|
|
151
|
-
expect(result.items).toHaveLength(10);
|
|
152
|
-
expect(result.items[0]?.id).toBe("t20");
|
|
153
|
-
expect(result.offset).toBe(20);
|
|
154
|
-
expect(result.hasMore).toBe(true);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("hasMore is false on last page", () => {
|
|
158
|
-
const tasks = Array.from({ length: 25 }, (_, i) => createTask({ id: `t${i}` }));
|
|
159
|
-
const result = projectTasksPaginated(tasks, "concise", 10, 20);
|
|
160
|
-
|
|
161
|
-
expect(result.items).toHaveLength(5);
|
|
162
|
-
expect(result.hasMore).toBe(false);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("enforces max limit of 100", () => {
|
|
166
|
-
const tasks = Array.from({ length: 200 }, (_, i) => createTask({ id: `t${i}` }));
|
|
167
|
-
const result = projectTasksPaginated(tasks, "concise", 150, 0);
|
|
168
|
-
|
|
169
|
-
expect(result.items).toHaveLength(100);
|
|
170
|
-
expect(result.limit).toBe(100);
|
|
171
|
-
expect(result.hasMore).toBe(true);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test("uses default limit of 20", () => {
|
|
175
|
-
const tasks = Array.from({ length: 50 }, (_, i) => createTask({ id: `t${i}` }));
|
|
176
|
-
const result = projectTasksPaginated(tasks, "concise");
|
|
177
|
-
|
|
178
|
-
expect(result.items).toHaveLength(20);
|
|
179
|
-
expect(result.limit).toBe(20);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
describe("projectInboxItem", () => {
|
|
184
|
-
test("concise format returns 3 essential fields", () => {
|
|
185
|
-
const item = createInboxItem();
|
|
186
|
-
const result = projectInboxItem(item, "concise");
|
|
187
|
-
|
|
188
|
-
expect(result).toEqual({
|
|
189
|
-
id: "inbox-1",
|
|
190
|
-
content: "Quick idea for later",
|
|
191
|
-
status: "pending",
|
|
192
|
-
});
|
|
193
|
-
expect(Object.keys(result)).toHaveLength(3);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test("standard format includes capturedAt and tags", () => {
|
|
197
|
-
const item = createInboxItem();
|
|
198
|
-
const result = projectInboxItem(item, "standard");
|
|
199
|
-
|
|
200
|
-
expect(result).toEqual({
|
|
201
|
-
id: "inbox-1",
|
|
202
|
-
content: "Quick idea for later",
|
|
203
|
-
status: "pending",
|
|
204
|
-
capturedAt: "2025-01-01T00:00:00.000Z",
|
|
205
|
-
tags: ["idea"],
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test("detailed format returns full item", () => {
|
|
210
|
-
const item = createInboxItem();
|
|
211
|
-
const result = projectInboxItem(item, "detailed");
|
|
212
|
-
|
|
213
|
-
expect(result).toBe(item);
|
|
214
|
-
expect(result).toHaveProperty("source");
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
describe("projectInboxItems", () => {
|
|
219
|
-
test("projects and limits inbox items", () => {
|
|
220
|
-
const items = [
|
|
221
|
-
createInboxItem({ id: "i1" }),
|
|
222
|
-
createInboxItem({ id: "i2" }),
|
|
223
|
-
createInboxItem({ id: "i3" }),
|
|
224
|
-
];
|
|
225
|
-
const result = projectInboxItems(items, "concise", 2);
|
|
226
|
-
|
|
227
|
-
expect(result).toHaveLength(2);
|
|
228
|
-
expect(result.map((i) => i.id)).toEqual(["i1", "i2"]);
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
describe("formatResponse", () => {
|
|
233
|
-
test("returns JSON for concise format", () => {
|
|
234
|
-
const data = { id: "1", name: "test" };
|
|
235
|
-
const result = formatResponse(data, "concise");
|
|
236
|
-
|
|
237
|
-
expect(result).toBe(JSON.stringify(data));
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test("returns JSON for standard format", () => {
|
|
241
|
-
const data = [{ id: "1" }, { id: "2" }];
|
|
242
|
-
const result = formatResponse(data, "standard");
|
|
243
|
-
|
|
244
|
-
expect(result).toBe(JSON.stringify(data));
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test("uses detailedFormatter for detailed format", () => {
|
|
248
|
-
const data = { id: "1", name: "test" };
|
|
249
|
-
const formatter = (d: typeof data | (typeof data)[]) => {
|
|
250
|
-
if (Array.isArray(d)) return d.map((i) => `${i.id}`).join(",");
|
|
251
|
-
return `ID: ${d.id}, Name: ${d.name}`;
|
|
252
|
-
};
|
|
253
|
-
const result = formatResponse(data, "detailed", formatter);
|
|
254
|
-
|
|
255
|
-
expect(result).toBe("ID: 1, Name: test");
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("falls back to JSON if no detailedFormatter provided", () => {
|
|
259
|
-
const data = { id: "1" };
|
|
260
|
-
const result = formatResponse(data, "detailed");
|
|
261
|
-
|
|
262
|
-
expect(result).toBe(JSON.stringify(data));
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
describe("applyPagination", () => {
|
|
267
|
-
test("applies limit and offset correctly", () => {
|
|
268
|
-
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
269
|
-
const result = applyPagination(items, 3, 2);
|
|
270
|
-
|
|
271
|
-
expect(result.items).toEqual([3, 4, 5]);
|
|
272
|
-
expect(result.hasMore).toBe(true);
|
|
273
|
-
expect(result.total).toBe(10);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
test("enforces max limit of 100", () => {
|
|
277
|
-
const items = Array.from({ length: 200 }, (_, i) => i);
|
|
278
|
-
const result = applyPagination(items, 150);
|
|
279
|
-
|
|
280
|
-
expect(result.items).toHaveLength(100);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
test("enforces min limit of 1", () => {
|
|
284
|
-
const items = [1, 2, 3];
|
|
285
|
-
const result = applyPagination(items, 0);
|
|
286
|
-
|
|
287
|
-
expect(result.items).toHaveLength(1);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
test("handles negative offset as 0", () => {
|
|
291
|
-
const items = [1, 2, 3];
|
|
292
|
-
const result = applyPagination(items, 10, -5);
|
|
293
|
-
|
|
294
|
-
expect(result.items).toEqual([1, 2, 3]);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test("uses default values", () => {
|
|
298
|
-
const items = Array.from({ length: 50 }, (_, i) => i);
|
|
299
|
-
const result = applyPagination(items);
|
|
300
|
-
|
|
301
|
-
expect(result.items).toHaveLength(20);
|
|
302
|
-
expect(result.items[0]).toBe(0);
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
describe("truncate", () => {
|
|
307
|
-
test("returns original string if under max length", () => {
|
|
308
|
-
const result = truncate("short", 100);
|
|
309
|
-
expect(result).toBe("short");
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
test("truncates and adds ellipsis", () => {
|
|
313
|
-
const result = truncate("this is a very long string", 10);
|
|
314
|
-
expect(result).toBe("this is...");
|
|
315
|
-
expect(result.length).toBe(10);
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
test("uses default max length of 100", () => {
|
|
319
|
-
const longString = "a".repeat(150);
|
|
320
|
-
const result = truncate(longString);
|
|
321
|
-
|
|
322
|
-
expect(result.length).toBe(100);
|
|
323
|
-
expect(result.endsWith("...")).toBe(true);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test("handles empty string", () => {
|
|
327
|
-
const result = truncate("");
|
|
328
|
-
expect(result).toBe("");
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
test("handles string exactly at max length", () => {
|
|
332
|
-
const str = "a".repeat(100);
|
|
333
|
-
const result = truncate(str, 100);
|
|
334
|
-
|
|
335
|
-
expect(result).toBe(str);
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
describe("summarizeList", () => {
|
|
340
|
-
test("returns displayed items and remaining count", () => {
|
|
341
|
-
const items = [
|
|
342
|
-
{ id: "1", name: "A" },
|
|
343
|
-
{ id: "2", name: "B" },
|
|
344
|
-
{ id: "3", name: "C" },
|
|
345
|
-
{ id: "4", name: "D" },
|
|
346
|
-
{ id: "5", name: "E" },
|
|
347
|
-
{ id: "6", name: "F" },
|
|
348
|
-
{ id: "7", name: "G" },
|
|
349
|
-
];
|
|
350
|
-
|
|
351
|
-
const result = summarizeList(items, 3, (i) => i.id);
|
|
352
|
-
|
|
353
|
-
expect(result.displayed).toHaveLength(3);
|
|
354
|
-
expect(result.remaining).toBe(4);
|
|
355
|
-
expect(result.remainingIds).toEqual(["4", "5", "6", "7"]);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
test("returns all items if under maxDisplay", () => {
|
|
359
|
-
const items = [{ id: "1" }, { id: "2" }];
|
|
360
|
-
const result = summarizeList(items, 5, (i) => i.id);
|
|
361
|
-
|
|
362
|
-
expect(result.displayed).toHaveLength(2);
|
|
363
|
-
expect(result.remaining).toBe(0);
|
|
364
|
-
expect(result.remainingIds).toEqual([]);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
test("uses default maxDisplay of 5", () => {
|
|
368
|
-
const items = Array.from({ length: 10 }, (_, i) => ({ id: `${i}` }));
|
|
369
|
-
const result = summarizeList(items, undefined, (i) => i.id);
|
|
370
|
-
|
|
371
|
-
expect(result.displayed).toHaveLength(5);
|
|
372
|
-
expect(result.remaining).toBe(5);
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
test("handles empty list", () => {
|
|
376
|
-
const result = summarizeList([], 5, (i: { id: string }) => i.id);
|
|
377
|
-
|
|
378
|
-
expect(result.displayed).toEqual([]);
|
|
379
|
-
expect(result.remaining).toBe(0);
|
|
380
|
-
expect(result.remainingIds).toEqual([]);
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
// Token efficiency verification tests
|
|
385
|
-
describe("token efficiency", () => {
|
|
386
|
-
test("concise task is significantly smaller than full task", () => {
|
|
387
|
-
const task = createTask({
|
|
388
|
-
description: "A very long description ".repeat(10),
|
|
389
|
-
estimate: { expected: 60, optimistic: 30, pessimistic: 120 },
|
|
390
|
-
dependencies: [
|
|
391
|
-
{ taskId: "d1", type: "blocked_by" },
|
|
392
|
-
{ taskId: "d2", type: "blocked_by" },
|
|
393
|
-
],
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
const concise = projectTask(task, "concise");
|
|
397
|
-
const full = projectTask(task, "detailed");
|
|
398
|
-
|
|
399
|
-
const conciseSize = JSON.stringify(concise).length;
|
|
400
|
-
const fullSize = JSON.stringify(full).length;
|
|
401
|
-
|
|
402
|
-
// Concise should be at least 50% smaller
|
|
403
|
-
expect(conciseSize).toBeLessThan(fullSize * 0.5);
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
test("concise list is significantly smaller than full list", () => {
|
|
407
|
-
const tasks = Array.from({ length: 20 }, (_, i) =>
|
|
408
|
-
createTask({
|
|
409
|
-
id: `task-${i}`,
|
|
410
|
-
title: `Task ${i}: Do something important`,
|
|
411
|
-
description: "Detailed description of what needs to be done ".repeat(5),
|
|
412
|
-
estimate: { expected: 60, optimistic: 30, pessimistic: 120 },
|
|
413
|
-
})
|
|
414
|
-
);
|
|
415
|
-
|
|
416
|
-
const concise = projectTasks(tasks, "concise");
|
|
417
|
-
const full = projectTasks(tasks, "detailed");
|
|
418
|
-
|
|
419
|
-
const conciseSize = JSON.stringify(concise).length;
|
|
420
|
-
const fullSize = JSON.stringify(full).length;
|
|
421
|
-
|
|
422
|
-
// For list of 20 tasks, concise should be 70%+ smaller
|
|
423
|
-
expect(conciseSize).toBeLessThan(fullSize * 0.4);
|
|
424
|
-
});
|
|
425
|
-
});
|