@task-mcp/shared 1.0.22 → 1.0.24
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/algorithms/critical-path.d.ts +47 -0
- package/dist/algorithms/critical-path.d.ts.map +1 -0
- package/dist/algorithms/critical-path.js +340 -0
- package/dist/algorithms/critical-path.js.map +1 -0
- package/dist/algorithms/critical-path.test.d.ts +2 -0
- package/dist/algorithms/critical-path.test.d.ts.map +1 -0
- package/dist/algorithms/critical-path.test.js +184 -0
- package/dist/algorithms/critical-path.test.js.map +1 -0
- package/dist/algorithms/dependency-integrity.d.ts +81 -0
- package/dist/algorithms/dependency-integrity.d.ts.map +1 -0
- package/dist/algorithms/dependency-integrity.js +209 -0
- package/dist/algorithms/dependency-integrity.js.map +1 -0
- package/dist/algorithms/dependency-integrity.test.d.ts +2 -0
- package/dist/algorithms/dependency-integrity.test.d.ts.map +1 -0
- package/dist/algorithms/dependency-integrity.test.js +296 -0
- package/dist/algorithms/dependency-integrity.test.js.map +1 -0
- package/dist/algorithms/index.d.ts +5 -0
- package/dist/algorithms/index.d.ts.map +1 -0
- package/dist/algorithms/index.js +5 -0
- package/dist/algorithms/index.js.map +1 -0
- package/dist/algorithms/tech-analysis.d.ts +106 -0
- package/dist/algorithms/tech-analysis.d.ts.map +1 -0
- package/dist/algorithms/tech-analysis.js +351 -0
- package/dist/algorithms/tech-analysis.js.map +1 -0
- package/dist/algorithms/tech-analysis.test.d.ts +2 -0
- package/dist/algorithms/tech-analysis.test.d.ts.map +1 -0
- package/dist/algorithms/tech-analysis.test.js +330 -0
- package/dist/algorithms/tech-analysis.test.js.map +1 -0
- package/dist/algorithms/topological-sort.d.ts +58 -0
- package/dist/algorithms/topological-sort.d.ts.map +1 -0
- package/dist/algorithms/topological-sort.js +201 -0
- package/dist/algorithms/topological-sort.js.map +1 -0
- package/dist/algorithms/topological-sort.test.d.ts +2 -0
- package/dist/algorithms/topological-sort.test.d.ts.map +1 -0
- package/dist/algorithms/topological-sort.test.js +154 -0
- package/dist/algorithms/topological-sort.test.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/inbox.d.ts +55 -0
- package/dist/schemas/inbox.d.ts.map +1 -0
- package/dist/schemas/inbox.js +25 -0
- package/dist/schemas/inbox.js.map +1 -0
- package/dist/schemas/index.d.ts +7 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +17 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/llm-guide.d.ts +147 -0
- package/dist/schemas/llm-guide.d.ts.map +1 -0
- package/dist/schemas/llm-guide.js +72 -0
- package/dist/schemas/llm-guide.js.map +1 -0
- package/dist/schemas/project.d.ts +177 -0
- package/dist/schemas/project.d.ts.map +1 -0
- package/dist/schemas/project.js +56 -0
- package/dist/schemas/project.js.map +1 -0
- package/dist/schemas/response-format.d.ts +148 -0
- package/dist/schemas/response-format.d.ts.map +1 -0
- package/dist/schemas/response-format.js +18 -0
- package/dist/schemas/response-format.js.map +1 -0
- package/dist/schemas/response-schema.d.ts +307 -0
- package/dist/schemas/response-schema.d.ts.map +1 -0
- package/dist/schemas/response-schema.js +78 -0
- package/dist/schemas/response-schema.js.map +1 -0
- package/dist/schemas/response-schema.test.d.ts +2 -0
- package/dist/schemas/response-schema.test.d.ts.map +1 -0
- package/dist/schemas/response-schema.test.js +256 -0
- package/dist/schemas/response-schema.test.js.map +1 -0
- package/dist/schemas/state.d.ts +17 -0
- package/dist/schemas/state.d.ts.map +1 -0
- package/dist/schemas/state.js +17 -0
- package/dist/schemas/state.js.map +1 -0
- package/dist/schemas/task.d.ts +881 -0
- package/dist/schemas/task.d.ts.map +1 -0
- package/dist/schemas/task.js +177 -0
- package/dist/schemas/task.js.map +1 -0
- package/dist/schemas/view.d.ts +143 -0
- package/dist/schemas/view.d.ts.map +1 -0
- package/dist/schemas/view.js +48 -0
- package/dist/schemas/view.js.map +1 -0
- package/dist/utils/dashboard-renderer.d.ts +93 -0
- package/dist/utils/dashboard-renderer.d.ts.map +1 -0
- package/dist/utils/dashboard-renderer.js +416 -0
- package/dist/utils/dashboard-renderer.js.map +1 -0
- package/dist/utils/dashboard-renderer.test.d.ts +2 -0
- package/dist/utils/dashboard-renderer.test.d.ts.map +1 -0
- package/dist/utils/dashboard-renderer.test.js +772 -0
- package/dist/utils/dashboard-renderer.test.js.map +1 -0
- package/dist/utils/date.d.ts +94 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +323 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/utils/date.test.d.ts +2 -0
- package/dist/utils/date.test.d.ts.map +1 -0
- package/dist/utils/date.test.js +276 -0
- package/dist/utils/date.test.js.map +1 -0
- package/dist/utils/hierarchy.d.ts +102 -0
- package/dist/utils/hierarchy.d.ts.map +1 -0
- package/dist/utils/hierarchy.js +236 -0
- package/dist/utils/hierarchy.js.map +1 -0
- package/dist/utils/hierarchy.test.d.ts +2 -0
- package/dist/utils/hierarchy.test.d.ts.map +1 -0
- package/dist/utils/hierarchy.test.js +423 -0
- package/dist/utils/hierarchy.test.js.map +1 -0
- package/dist/utils/id.d.ts +60 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +118 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/id.test.d.ts +2 -0
- package/dist/utils/id.test.d.ts.map +1 -0
- package/dist/utils/id.test.js +193 -0
- package/dist/utils/id.test.js.map +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +34 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/natural-language.d.ts +111 -0
- package/dist/utils/natural-language.d.ts.map +1 -0
- package/dist/utils/natural-language.js +297 -0
- package/dist/utils/natural-language.js.map +1 -0
- package/dist/utils/natural-language.test.d.ts +2 -0
- package/dist/utils/natural-language.test.d.ts.map +1 -0
- package/dist/utils/natural-language.test.js +197 -0
- package/dist/utils/natural-language.test.js.map +1 -0
- package/dist/utils/priority-queue.d.ts +17 -0
- package/dist/utils/priority-queue.d.ts.map +1 -0
- package/dist/utils/priority-queue.js +62 -0
- package/dist/utils/priority-queue.js.map +1 -0
- 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 +65 -0
- package/dist/utils/projection.d.ts.map +1 -0
- package/dist/utils/projection.js +180 -0
- package/dist/utils/projection.js.map +1 -0
- package/dist/utils/projection.test.d.ts +2 -0
- package/dist/utils/projection.test.d.ts.map +1 -0
- package/dist/utils/projection.test.js +341 -0
- package/dist/utils/projection.test.js.map +1 -0
- package/dist/utils/terminal-ui.d.ts +208 -0
- package/dist/utils/terminal-ui.d.ts.map +1 -0
- package/dist/utils/terminal-ui.js +614 -0
- package/dist/utils/terminal-ui.js.map +1 -0
- package/dist/utils/terminal-ui.test.d.ts +2 -0
- package/dist/utils/terminal-ui.test.d.ts.map +1 -0
- package/dist/utils/terminal-ui.test.js +683 -0
- package/dist/utils/terminal-ui.test.js.map +1 -0
- package/dist/utils/workspace.d.ts +102 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +183 -0
- package/dist/utils/workspace.js.map +1 -0
- 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 +5 -1
- package/src/algorithms/critical-path.test.ts +227 -0
- package/src/algorithms/critical-path.ts +14 -34
- package/src/algorithms/dependency-integrity.test.ts +335 -0
- package/src/algorithms/dependency-integrity.ts +4 -13
- package/src/algorithms/tech-analysis.test.ts +405 -0
- package/src/algorithms/tech-analysis.ts +27 -27
- package/src/algorithms/topological-sort.test.ts +182 -0
- package/src/algorithms/topological-sort.ts +6 -10
- package/src/schemas/index.ts +2 -13
- package/src/schemas/response-format.ts +6 -6
- package/src/schemas/response-schema.test.ts +314 -0
- package/src/schemas/response-schema.ts +25 -20
- package/src/schemas/task.ts +4 -22
- package/src/utils/dashboard-renderer.test.ts +976 -0
- package/src/utils/dashboard-renderer.ts +27 -59
- package/src/utils/date.test.ts +329 -0
- package/src/utils/date.ts +2 -10
- package/src/utils/hierarchy.test.ts +488 -0
- package/src/utils/hierarchy.ts +4 -5
- package/src/utils/id.test.ts +235 -0
- package/src/utils/index.ts +7 -1
- package/src/utils/natural-language.test.ts +234 -0
- package/src/utils/priority-queue.test.ts +103 -0
- package/src/utils/projection.test.ts +430 -0
- package/src/utils/terminal-ui.test.ts +831 -0
- package/src/utils/terminal-ui.ts +53 -54
- package/src/utils/workspace.test.ts +125 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
priorityToNumber,
|
|
4
|
+
topologicalSort,
|
|
5
|
+
wouldCreateCycle,
|
|
6
|
+
findDependents,
|
|
7
|
+
findDependencies,
|
|
8
|
+
} from "./topological-sort.js";
|
|
9
|
+
import type { Task } from "../schemas/task.js";
|
|
10
|
+
|
|
11
|
+
// Helper to create mock tasks
|
|
12
|
+
function createTask(id: string, priority: string = "medium", deps: string[] = []): Task {
|
|
13
|
+
return {
|
|
14
|
+
id,
|
|
15
|
+
title: `Task ${id}`,
|
|
16
|
+
status: "pending",
|
|
17
|
+
priority: priority as Task["priority"],
|
|
18
|
+
workspace: "test-workspace",
|
|
19
|
+
createdAt: new Date().toISOString(),
|
|
20
|
+
updatedAt: new Date().toISOString(),
|
|
21
|
+
dependencies: deps.map((depId) => ({ taskId: depId, type: "blocked_by" as const })),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("priorityToNumber", () => {
|
|
26
|
+
test("converts critical to 4", () => {
|
|
27
|
+
expect(priorityToNumber("critical")).toBe(4);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("converts high to 3", () => {
|
|
31
|
+
expect(priorityToNumber("high")).toBe(3);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("converts medium to 2", () => {
|
|
35
|
+
expect(priorityToNumber("medium")).toBe(2);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("converts low to 1", () => {
|
|
39
|
+
expect(priorityToNumber("low")).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("defaults to 2 for unknown priority", () => {
|
|
43
|
+
expect(priorityToNumber("unknown")).toBe(2);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("topologicalSort", () => {
|
|
48
|
+
test("returns empty array for empty input", () => {
|
|
49
|
+
const result = topologicalSort([]);
|
|
50
|
+
expect(result).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("returns single task unchanged", () => {
|
|
54
|
+
const tasks = [createTask("A")];
|
|
55
|
+
const result = topologicalSort(tasks);
|
|
56
|
+
expect(result.length).toBe(1);
|
|
57
|
+
expect(result[0]!.id).toBe("A");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("sorts by dependency order", () => {
|
|
61
|
+
// B depends on A, so A should come first
|
|
62
|
+
const tasks = [createTask("B", "high", ["A"]), createTask("A", "low")];
|
|
63
|
+
const result = topologicalSort(tasks);
|
|
64
|
+
expect(result[0]!.id).toBe("A");
|
|
65
|
+
expect(result[1]!.id).toBe("B");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("respects priority when no dependencies", () => {
|
|
69
|
+
const tasks = [createTask("A", "low"), createTask("B", "critical"), createTask("C", "high")];
|
|
70
|
+
const result = topologicalSort(tasks);
|
|
71
|
+
expect(result[0]!.id).toBe("B"); // critical first
|
|
72
|
+
expect(result[1]!.id).toBe("C"); // then high
|
|
73
|
+
expect(result[2]!.id).toBe("A"); // then low
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("handles chain of dependencies", () => {
|
|
77
|
+
// C -> B -> A (C depends on B, B depends on A)
|
|
78
|
+
const tasks = [
|
|
79
|
+
createTask("C", "critical", ["B"]),
|
|
80
|
+
createTask("B", "high", ["A"]),
|
|
81
|
+
createTask("A", "low"),
|
|
82
|
+
];
|
|
83
|
+
const result = topologicalSort(tasks);
|
|
84
|
+
expect(result.map((t) => t.id)).toEqual(["A", "B", "C"]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("throws on circular dependency", () => {
|
|
88
|
+
// A -> B -> A
|
|
89
|
+
const tasks = [createTask("A", "medium", ["B"]), createTask("B", "medium", ["A"])];
|
|
90
|
+
expect(() => topologicalSort(tasks)).toThrow(/Circular dependency/);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("handles diamond dependency", () => {
|
|
94
|
+
// D depends on B and C, both depend on A
|
|
95
|
+
const tasks = [
|
|
96
|
+
createTask("D", "medium", ["B", "C"]),
|
|
97
|
+
createTask("B", "high", ["A"]),
|
|
98
|
+
createTask("C", "low", ["A"]),
|
|
99
|
+
createTask("A", "medium"),
|
|
100
|
+
];
|
|
101
|
+
const result = topologicalSort(tasks);
|
|
102
|
+
// A must come first, D must come last
|
|
103
|
+
expect(result[0]!.id).toBe("A");
|
|
104
|
+
expect(result[3]!.id).toBe("D");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("wouldCreateCycle", () => {
|
|
109
|
+
test("returns false for valid dependency", () => {
|
|
110
|
+
const tasks = [createTask("A"), createTask("B")];
|
|
111
|
+
// Adding B blocked_by A should not create cycle
|
|
112
|
+
expect(wouldCreateCycle(tasks, "B", "A")).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("returns true for direct cycle", () => {
|
|
116
|
+
const tasks = [createTask("A", "medium", ["B"]), createTask("B")];
|
|
117
|
+
// B is already blocking A, so A blocking B would create cycle
|
|
118
|
+
expect(wouldCreateCycle(tasks, "B", "A")).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("returns true for indirect cycle", () => {
|
|
122
|
+
const tasks = [
|
|
123
|
+
createTask("A", "medium", ["B"]),
|
|
124
|
+
createTask("B", "medium", ["C"]),
|
|
125
|
+
createTask("C"),
|
|
126
|
+
];
|
|
127
|
+
// A <- B <- C, adding C <- A would create cycle
|
|
128
|
+
expect(wouldCreateCycle(tasks, "C", "A")).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("findDependents", () => {
|
|
133
|
+
test("returns empty for task with no dependents", () => {
|
|
134
|
+
const tasks = [createTask("A"), createTask("B")];
|
|
135
|
+
const result = findDependents(tasks, "A");
|
|
136
|
+
expect(result).toEqual([]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("finds direct dependents", () => {
|
|
140
|
+
const tasks = [
|
|
141
|
+
createTask("A"),
|
|
142
|
+
createTask("B", "medium", ["A"]),
|
|
143
|
+
createTask("C", "medium", ["A"]),
|
|
144
|
+
];
|
|
145
|
+
const result = findDependents(tasks, "A");
|
|
146
|
+
expect(result.map((t) => t.id).sort()).toEqual(["B", "C"]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("finds transitive dependents", () => {
|
|
150
|
+
const tasks = [
|
|
151
|
+
createTask("A"),
|
|
152
|
+
createTask("B", "medium", ["A"]),
|
|
153
|
+
createTask("C", "medium", ["B"]),
|
|
154
|
+
];
|
|
155
|
+
const result = findDependents(tasks, "A");
|
|
156
|
+
expect(result.map((t) => t.id).sort()).toEqual(["B", "C"]);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("findDependencies", () => {
|
|
161
|
+
test("returns empty for task with no dependencies", () => {
|
|
162
|
+
const tasks = [createTask("A"), createTask("B")];
|
|
163
|
+
const result = findDependencies(tasks, "A");
|
|
164
|
+
expect(result).toEqual([]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("finds direct dependencies", () => {
|
|
168
|
+
const tasks = [createTask("A"), createTask("B"), createTask("C", "medium", ["A", "B"])];
|
|
169
|
+
const result = findDependencies(tasks, "C");
|
|
170
|
+
expect(result.map((t) => t.id).sort()).toEqual(["A", "B"]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("finds transitive dependencies", () => {
|
|
174
|
+
const tasks = [
|
|
175
|
+
createTask("A"),
|
|
176
|
+
createTask("B", "medium", ["A"]),
|
|
177
|
+
createTask("C", "medium", ["B"]),
|
|
178
|
+
];
|
|
179
|
+
const result = findDependencies(tasks, "C");
|
|
180
|
+
expect(result.map((t) => t.id).sort()).toEqual(["A", "B"]);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -115,20 +115,13 @@ export function topologicalSort(tasks: Task[]): Task[] {
|
|
|
115
115
|
/**
|
|
116
116
|
* Detect if adding a dependency would create a cycle
|
|
117
117
|
*/
|
|
118
|
-
export function wouldCreateCycle(
|
|
119
|
-
tasks: Task[],
|
|
120
|
-
fromId: string,
|
|
121
|
-
toId: string
|
|
122
|
-
): boolean {
|
|
118
|
+
export function wouldCreateCycle(tasks: Task[], fromId: string, toId: string): boolean {
|
|
123
119
|
// Create a temporary task list with the new dependency
|
|
124
120
|
const tempTasks = tasks.map((t) => {
|
|
125
121
|
if (t.id === fromId) {
|
|
126
122
|
return {
|
|
127
123
|
...t,
|
|
128
|
-
dependencies: [
|
|
129
|
-
...(t.dependencies ?? []),
|
|
130
|
-
{ taskId: toId, type: "blocked_by" as const },
|
|
131
|
-
],
|
|
124
|
+
dependencies: [...(t.dependencies ?? []), { taskId: toId, type: "blocked_by" as const }],
|
|
132
125
|
};
|
|
133
126
|
}
|
|
134
127
|
return t;
|
|
@@ -173,7 +166,10 @@ export function buildDependencyIndices(tasks: Task[]): {
|
|
|
173
166
|
.map((d) => d.taskId);
|
|
174
167
|
|
|
175
168
|
// Store direct dependencies for this task
|
|
176
|
-
dependenciesIndex.set(
|
|
169
|
+
dependenciesIndex.set(
|
|
170
|
+
task.id,
|
|
171
|
+
deps.filter((depId) => taskMap.has(depId))
|
|
172
|
+
);
|
|
177
173
|
|
|
178
174
|
// Update dependents index (reverse lookup)
|
|
179
175
|
for (const depId of deps) {
|
package/src/schemas/index.ts
CHANGED
|
@@ -19,21 +19,10 @@ export {
|
|
|
19
19
|
} from "./task.js";
|
|
20
20
|
|
|
21
21
|
// View schemas
|
|
22
|
-
export {
|
|
23
|
-
SmartViewFilter,
|
|
24
|
-
SortField,
|
|
25
|
-
SortOrder,
|
|
26
|
-
SmartView,
|
|
27
|
-
BuiltInView,
|
|
28
|
-
} from "./view.js";
|
|
22
|
+
export { SmartViewFilter, SortField, SortOrder, SmartView, BuiltInView } from "./view.js";
|
|
29
23
|
|
|
30
24
|
// Inbox schemas
|
|
31
|
-
export {
|
|
32
|
-
InboxStatus,
|
|
33
|
-
InboxItem,
|
|
34
|
-
InboxCreateInput,
|
|
35
|
-
InboxUpdateInput,
|
|
36
|
-
} from "./inbox.js";
|
|
25
|
+
export { InboxStatus, InboxItem, InboxCreateInput, InboxUpdateInput } from "./inbox.js";
|
|
37
26
|
|
|
38
27
|
// Response format schemas (token optimization)
|
|
39
28
|
export {
|
|
@@ -129,9 +129,9 @@ export interface DashboardPriorityBreakdown {
|
|
|
129
129
|
|
|
130
130
|
// Dependency metrics
|
|
131
131
|
export interface DashboardDependencyMetrics {
|
|
132
|
-
ready: number;
|
|
133
|
-
blocked: number;
|
|
134
|
-
noDeps: number;
|
|
132
|
+
ready: number; // No dependencies or all satisfied
|
|
133
|
+
blocked: number; // Has unsatisfied dependencies
|
|
134
|
+
noDeps: number; // Tasks with no dependencies
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// Next task recommendation
|
|
@@ -139,12 +139,12 @@ export interface DashboardNextTask {
|
|
|
139
139
|
id: string;
|
|
140
140
|
title: string;
|
|
141
141
|
priority: string;
|
|
142
|
-
reason: string;
|
|
142
|
+
reason: string; // Why this task is recommended
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// Critical path info
|
|
146
146
|
export interface DashboardCriticalPath {
|
|
147
|
-
length: number;
|
|
147
|
+
length: number; // Total duration in minutes
|
|
148
148
|
taskCount: number; // Number of tasks on critical path
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -162,7 +162,7 @@ export interface DashboardStatusBreakdown {
|
|
|
162
162
|
export interface DashboardSubtaskProgress {
|
|
163
163
|
completed: number;
|
|
164
164
|
total: number;
|
|
165
|
-
pct: number;
|
|
165
|
+
pct: number; // 0-100
|
|
166
166
|
inProgress: number;
|
|
167
167
|
pending: number;
|
|
168
168
|
blocked: number;
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
ResponseType,
|
|
4
|
+
ResponsePriority,
|
|
5
|
+
ResponseOption,
|
|
6
|
+
QuestionResponse,
|
|
7
|
+
SuggestionResponse,
|
|
8
|
+
ConfirmationResponse,
|
|
9
|
+
GenerateResponseInput,
|
|
10
|
+
} from "./response-schema.js";
|
|
11
|
+
|
|
12
|
+
describe("Response Schema", () => {
|
|
13
|
+
describe("ResponseType", () => {
|
|
14
|
+
it("should accept valid response types", () => {
|
|
15
|
+
expect(() => ResponseType.parse("question")).not.toThrow();
|
|
16
|
+
expect(() => ResponseType.parse("suggestion")).not.toThrow();
|
|
17
|
+
expect(() => ResponseType.parse("confirmation")).not.toThrow();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should reject invalid response types", () => {
|
|
21
|
+
expect(() => ResponseType.parse("invalid")).toThrow();
|
|
22
|
+
expect(() => ResponseType.parse("ask")).toThrow();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("ResponsePriority", () => {
|
|
27
|
+
it("should accept valid priority levels", () => {
|
|
28
|
+
expect(() => ResponsePriority.parse("low")).not.toThrow();
|
|
29
|
+
expect(() => ResponsePriority.parse("medium")).not.toThrow();
|
|
30
|
+
expect(() => ResponsePriority.parse("high")).not.toThrow();
|
|
31
|
+
expect(() => ResponsePriority.parse("critical")).not.toThrow();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should reject invalid priority levels", () => {
|
|
35
|
+
expect(() => ResponsePriority.parse("urgent")).toThrow();
|
|
36
|
+
expect(() => ResponsePriority.parse("normal")).toThrow();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("ResponseOption", () => {
|
|
41
|
+
it("should validate option with all fields", () => {
|
|
42
|
+
const option = {
|
|
43
|
+
label: "High Priority",
|
|
44
|
+
value: "high",
|
|
45
|
+
description: "For urgent tasks",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
expect(() => ResponseOption.parse(option)).not.toThrow();
|
|
49
|
+
const parsed = ResponseOption.parse(option);
|
|
50
|
+
expect(parsed.label).toBe("High Priority");
|
|
51
|
+
expect(parsed.value).toBe("high");
|
|
52
|
+
expect(parsed.description).toBe("For urgent tasks");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should validate option without description", () => {
|
|
56
|
+
const option = {
|
|
57
|
+
label: "Low Priority",
|
|
58
|
+
value: "low",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
expect(() => ResponseOption.parse(option)).not.toThrow();
|
|
62
|
+
const parsed = ResponseOption.parse(option);
|
|
63
|
+
expect(parsed.label).toBe("Low Priority");
|
|
64
|
+
expect(parsed.value).toBe("low");
|
|
65
|
+
expect(parsed.description).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should reject option without required fields", () => {
|
|
69
|
+
expect(() => ResponseOption.parse({ label: "Test" })).toThrow();
|
|
70
|
+
expect(() => ResponseOption.parse({ value: "test" })).toThrow();
|
|
71
|
+
expect(() => ResponseOption.parse({})).toThrow();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("QuestionResponse", () => {
|
|
76
|
+
it("should validate question with all fields", () => {
|
|
77
|
+
const question = {
|
|
78
|
+
type: "question" as const,
|
|
79
|
+
message: "What should we do?",
|
|
80
|
+
context: "Need clarification on approach",
|
|
81
|
+
priority: "high" as const,
|
|
82
|
+
options: [
|
|
83
|
+
{ label: "Option A", value: "a" },
|
|
84
|
+
{ label: "Option B", value: "b" },
|
|
85
|
+
],
|
|
86
|
+
defaultOption: "a",
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
expect(() => QuestionResponse.parse(question)).not.toThrow();
|
|
90
|
+
const parsed = QuestionResponse.parse(question);
|
|
91
|
+
expect(parsed.type).toBe("question");
|
|
92
|
+
expect(parsed.message).toBe("What should we do?");
|
|
93
|
+
expect(parsed.options).toHaveLength(2);
|
|
94
|
+
expect(parsed.defaultOption).toBe("a");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should validate question with minimal fields", () => {
|
|
98
|
+
const question = {
|
|
99
|
+
type: "question" as const,
|
|
100
|
+
message: "Proceed?",
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
expect(() => QuestionResponse.parse(question)).not.toThrow();
|
|
104
|
+
const parsed = QuestionResponse.parse(question);
|
|
105
|
+
expect(parsed.type).toBe("question");
|
|
106
|
+
expect(parsed.message).toBe("Proceed?");
|
|
107
|
+
expect(parsed.context).toBeUndefined();
|
|
108
|
+
expect(parsed.options).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should reject question without message", () => {
|
|
112
|
+
expect(() => QuestionResponse.parse({ type: "question" })).toThrow();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should reject question with wrong type", () => {
|
|
116
|
+
expect(() =>
|
|
117
|
+
QuestionResponse.parse({
|
|
118
|
+
type: "suggestion",
|
|
119
|
+
message: "Test",
|
|
120
|
+
})
|
|
121
|
+
).toThrow();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("SuggestionResponse", () => {
|
|
126
|
+
it("should validate suggestion with all fields", () => {
|
|
127
|
+
const suggestion = {
|
|
128
|
+
type: "suggestion" as const,
|
|
129
|
+
message: "Consider breaking down this task",
|
|
130
|
+
context: "Task is complex",
|
|
131
|
+
priority: "medium" as const,
|
|
132
|
+
options: [
|
|
133
|
+
{ label: "3 subtasks", value: "sub_3" },
|
|
134
|
+
{ label: "5 subtasks", value: "sub_5" },
|
|
135
|
+
],
|
|
136
|
+
reasoning: "Based on complexity analysis",
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
expect(() => SuggestionResponse.parse(suggestion)).not.toThrow();
|
|
140
|
+
const parsed = SuggestionResponse.parse(suggestion);
|
|
141
|
+
expect(parsed.type).toBe("suggestion");
|
|
142
|
+
expect(parsed.options).toHaveLength(2);
|
|
143
|
+
expect(parsed.reasoning).toBe("Based on complexity analysis");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should validate suggestion with minimal fields", () => {
|
|
147
|
+
const suggestion = {
|
|
148
|
+
type: "suggestion" as const,
|
|
149
|
+
message: "Try this",
|
|
150
|
+
options: [{ label: "Do it", value: "yes" }],
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
expect(() => SuggestionResponse.parse(suggestion)).not.toThrow();
|
|
154
|
+
const parsed = SuggestionResponse.parse(suggestion);
|
|
155
|
+
expect(parsed.options).toHaveLength(1);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should reject suggestion without options", () => {
|
|
159
|
+
expect(() =>
|
|
160
|
+
SuggestionResponse.parse({
|
|
161
|
+
type: "suggestion",
|
|
162
|
+
message: "Test",
|
|
163
|
+
})
|
|
164
|
+
).toThrow();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should reject suggestion with empty options array", () => {
|
|
168
|
+
expect(() =>
|
|
169
|
+
SuggestionResponse.parse({
|
|
170
|
+
type: "suggestion",
|
|
171
|
+
message: "Test",
|
|
172
|
+
options: [],
|
|
173
|
+
})
|
|
174
|
+
).toThrow();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("ConfirmationResponse", () => {
|
|
179
|
+
it("should validate confirmation with all fields", () => {
|
|
180
|
+
const confirmation = {
|
|
181
|
+
type: "confirmation" as const,
|
|
182
|
+
message: "Are you sure?",
|
|
183
|
+
action: "Delete all completed tasks",
|
|
184
|
+
context: "This operation is irreversible",
|
|
185
|
+
priority: "critical" as const,
|
|
186
|
+
consequences: ["10 tasks will be removed", "Cannot be undone"],
|
|
187
|
+
defaultConfirm: false,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
expect(() => ConfirmationResponse.parse(confirmation)).not.toThrow();
|
|
191
|
+
const parsed = ConfirmationResponse.parse(confirmation);
|
|
192
|
+
expect(parsed.type).toBe("confirmation");
|
|
193
|
+
expect(parsed.action).toBe("Delete all completed tasks");
|
|
194
|
+
expect(parsed.consequences).toHaveLength(2);
|
|
195
|
+
expect(parsed.defaultConfirm).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should validate confirmation with minimal fields", () => {
|
|
199
|
+
const confirmation = {
|
|
200
|
+
type: "confirmation" as const,
|
|
201
|
+
message: "Delete this?",
|
|
202
|
+
action: "Delete task",
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
expect(() => ConfirmationResponse.parse(confirmation)).not.toThrow();
|
|
206
|
+
const parsed = ConfirmationResponse.parse(confirmation);
|
|
207
|
+
expect(parsed.action).toBe("Delete task");
|
|
208
|
+
expect(parsed.consequences).toBeUndefined();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should reject confirmation without action", () => {
|
|
212
|
+
expect(() =>
|
|
213
|
+
ConfirmationResponse.parse({
|
|
214
|
+
type: "confirmation",
|
|
215
|
+
message: "Confirm?",
|
|
216
|
+
})
|
|
217
|
+
).toThrow();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("should reject confirmation without message", () => {
|
|
221
|
+
expect(() =>
|
|
222
|
+
ConfirmationResponse.parse({
|
|
223
|
+
type: "confirmation",
|
|
224
|
+
action: "Do something",
|
|
225
|
+
})
|
|
226
|
+
).toThrow();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe("GenerateResponseInput", () => {
|
|
231
|
+
it("should validate input for question type", () => {
|
|
232
|
+
const input = {
|
|
233
|
+
type: "question" as const,
|
|
234
|
+
message: "What to do?",
|
|
235
|
+
options: [{ label: "Yes", value: "yes" }],
|
|
236
|
+
defaultOption: "yes",
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
expect(() => GenerateResponseInput.parse(input)).not.toThrow();
|
|
240
|
+
const parsed = GenerateResponseInput.parse(input);
|
|
241
|
+
expect(parsed.type).toBe("question");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should validate input for suggestion type", () => {
|
|
245
|
+
const input = {
|
|
246
|
+
type: "suggestion" as const,
|
|
247
|
+
message: "Try this",
|
|
248
|
+
options: [{ label: "Option", value: "opt" }],
|
|
249
|
+
reasoning: "Because reasons",
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
expect(() => GenerateResponseInput.parse(input)).not.toThrow();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("should validate input for confirmation type", () => {
|
|
256
|
+
const input = {
|
|
257
|
+
type: "confirmation" as const,
|
|
258
|
+
message: "Sure?",
|
|
259
|
+
action: "Delete",
|
|
260
|
+
consequences: ["Gone forever"],
|
|
261
|
+
defaultConfirm: false,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
expect(() => GenerateResponseInput.parse(input)).not.toThrow();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("should use default priority of medium", () => {
|
|
268
|
+
const input = {
|
|
269
|
+
type: "question" as const,
|
|
270
|
+
message: "Test?",
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const parsed = GenerateResponseInput.parse(input);
|
|
274
|
+
expect(parsed.priority).toBe("medium");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("should allow custom priority", () => {
|
|
278
|
+
const input = {
|
|
279
|
+
type: "question" as const,
|
|
280
|
+
message: "Test?",
|
|
281
|
+
priority: "critical" as const,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const parsed = GenerateResponseInput.parse(input);
|
|
285
|
+
expect(parsed.priority).toBe("critical");
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe("Type discrimination", () => {
|
|
290
|
+
it("should differentiate response types at compile time", () => {
|
|
291
|
+
const question: QuestionResponse = {
|
|
292
|
+
type: "question",
|
|
293
|
+
message: "Test",
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const suggestion: SuggestionResponse = {
|
|
297
|
+
type: "suggestion",
|
|
298
|
+
message: "Test",
|
|
299
|
+
options: [{ label: "A", value: "a" }],
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const confirmation: ConfirmationResponse = {
|
|
303
|
+
type: "confirmation",
|
|
304
|
+
message: "Test",
|
|
305
|
+
action: "Do it",
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// TypeScript should allow this
|
|
309
|
+
expect(question.type).toBe("question");
|
|
310
|
+
expect(suggestion.type).toBe("suggestion");
|
|
311
|
+
expect(confirmation.type).toBe("confirmation");
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
});
|
|
@@ -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>;
|