@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.
Files changed (184) hide show
  1. package/dist/algorithms/critical-path.d.ts +47 -0
  2. package/dist/algorithms/critical-path.d.ts.map +1 -0
  3. package/dist/algorithms/critical-path.js +340 -0
  4. package/dist/algorithms/critical-path.js.map +1 -0
  5. package/dist/algorithms/critical-path.test.d.ts +2 -0
  6. package/dist/algorithms/critical-path.test.d.ts.map +1 -0
  7. package/dist/algorithms/critical-path.test.js +184 -0
  8. package/dist/algorithms/critical-path.test.js.map +1 -0
  9. package/dist/algorithms/dependency-integrity.d.ts +81 -0
  10. package/dist/algorithms/dependency-integrity.d.ts.map +1 -0
  11. package/dist/algorithms/dependency-integrity.js +209 -0
  12. package/dist/algorithms/dependency-integrity.js.map +1 -0
  13. package/dist/algorithms/dependency-integrity.test.d.ts +2 -0
  14. package/dist/algorithms/dependency-integrity.test.d.ts.map +1 -0
  15. package/dist/algorithms/dependency-integrity.test.js +296 -0
  16. package/dist/algorithms/dependency-integrity.test.js.map +1 -0
  17. package/dist/algorithms/index.d.ts +5 -0
  18. package/dist/algorithms/index.d.ts.map +1 -0
  19. package/dist/algorithms/index.js +5 -0
  20. package/dist/algorithms/index.js.map +1 -0
  21. package/dist/algorithms/tech-analysis.d.ts +106 -0
  22. package/dist/algorithms/tech-analysis.d.ts.map +1 -0
  23. package/dist/algorithms/tech-analysis.js +351 -0
  24. package/dist/algorithms/tech-analysis.js.map +1 -0
  25. package/dist/algorithms/tech-analysis.test.d.ts +2 -0
  26. package/dist/algorithms/tech-analysis.test.d.ts.map +1 -0
  27. package/dist/algorithms/tech-analysis.test.js +330 -0
  28. package/dist/algorithms/tech-analysis.test.js.map +1 -0
  29. package/dist/algorithms/topological-sort.d.ts +58 -0
  30. package/dist/algorithms/topological-sort.d.ts.map +1 -0
  31. package/dist/algorithms/topological-sort.js +201 -0
  32. package/dist/algorithms/topological-sort.js.map +1 -0
  33. package/dist/algorithms/topological-sort.test.d.ts +2 -0
  34. package/dist/algorithms/topological-sort.test.d.ts.map +1 -0
  35. package/dist/algorithms/topological-sort.test.js +154 -0
  36. package/dist/algorithms/topological-sort.test.js.map +1 -0
  37. package/dist/index.d.ts +4 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +7 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/schemas/inbox.d.ts +55 -0
  42. package/dist/schemas/inbox.d.ts.map +1 -0
  43. package/dist/schemas/inbox.js +25 -0
  44. package/dist/schemas/inbox.js.map +1 -0
  45. package/dist/schemas/index.d.ts +7 -0
  46. package/dist/schemas/index.d.ts.map +1 -0
  47. package/dist/schemas/index.js +17 -0
  48. package/dist/schemas/index.js.map +1 -0
  49. package/dist/schemas/llm-guide.d.ts +147 -0
  50. package/dist/schemas/llm-guide.d.ts.map +1 -0
  51. package/dist/schemas/llm-guide.js +72 -0
  52. package/dist/schemas/llm-guide.js.map +1 -0
  53. package/dist/schemas/project.d.ts +177 -0
  54. package/dist/schemas/project.d.ts.map +1 -0
  55. package/dist/schemas/project.js +56 -0
  56. package/dist/schemas/project.js.map +1 -0
  57. package/dist/schemas/response-format.d.ts +148 -0
  58. package/dist/schemas/response-format.d.ts.map +1 -0
  59. package/dist/schemas/response-format.js +18 -0
  60. package/dist/schemas/response-format.js.map +1 -0
  61. package/dist/schemas/response-schema.d.ts +307 -0
  62. package/dist/schemas/response-schema.d.ts.map +1 -0
  63. package/dist/schemas/response-schema.js +78 -0
  64. package/dist/schemas/response-schema.js.map +1 -0
  65. package/dist/schemas/response-schema.test.d.ts +2 -0
  66. package/dist/schemas/response-schema.test.d.ts.map +1 -0
  67. package/dist/schemas/response-schema.test.js +256 -0
  68. package/dist/schemas/response-schema.test.js.map +1 -0
  69. package/dist/schemas/state.d.ts +17 -0
  70. package/dist/schemas/state.d.ts.map +1 -0
  71. package/dist/schemas/state.js +17 -0
  72. package/dist/schemas/state.js.map +1 -0
  73. package/dist/schemas/task.d.ts +881 -0
  74. package/dist/schemas/task.d.ts.map +1 -0
  75. package/dist/schemas/task.js +177 -0
  76. package/dist/schemas/task.js.map +1 -0
  77. package/dist/schemas/view.d.ts +143 -0
  78. package/dist/schemas/view.d.ts.map +1 -0
  79. package/dist/schemas/view.js +48 -0
  80. package/dist/schemas/view.js.map +1 -0
  81. package/dist/utils/dashboard-renderer.d.ts +93 -0
  82. package/dist/utils/dashboard-renderer.d.ts.map +1 -0
  83. package/dist/utils/dashboard-renderer.js +416 -0
  84. package/dist/utils/dashboard-renderer.js.map +1 -0
  85. package/dist/utils/dashboard-renderer.test.d.ts +2 -0
  86. package/dist/utils/dashboard-renderer.test.d.ts.map +1 -0
  87. package/dist/utils/dashboard-renderer.test.js +772 -0
  88. package/dist/utils/dashboard-renderer.test.js.map +1 -0
  89. package/dist/utils/date.d.ts +94 -0
  90. package/dist/utils/date.d.ts.map +1 -0
  91. package/dist/utils/date.js +323 -0
  92. package/dist/utils/date.js.map +1 -0
  93. package/dist/utils/date.test.d.ts +2 -0
  94. package/dist/utils/date.test.d.ts.map +1 -0
  95. package/dist/utils/date.test.js +276 -0
  96. package/dist/utils/date.test.js.map +1 -0
  97. package/dist/utils/hierarchy.d.ts +102 -0
  98. package/dist/utils/hierarchy.d.ts.map +1 -0
  99. package/dist/utils/hierarchy.js +236 -0
  100. package/dist/utils/hierarchy.js.map +1 -0
  101. package/dist/utils/hierarchy.test.d.ts +2 -0
  102. package/dist/utils/hierarchy.test.d.ts.map +1 -0
  103. package/dist/utils/hierarchy.test.js +423 -0
  104. package/dist/utils/hierarchy.test.js.map +1 -0
  105. package/dist/utils/id.d.ts +60 -0
  106. package/dist/utils/id.d.ts.map +1 -0
  107. package/dist/utils/id.js +118 -0
  108. package/dist/utils/id.js.map +1 -0
  109. package/dist/utils/id.test.d.ts +2 -0
  110. package/dist/utils/id.test.d.ts.map +1 -0
  111. package/dist/utils/id.test.js +193 -0
  112. package/dist/utils/id.test.js.map +1 -0
  113. package/dist/utils/index.d.ts +12 -0
  114. package/dist/utils/index.d.ts.map +1 -0
  115. package/dist/utils/index.js +34 -0
  116. package/dist/utils/index.js.map +1 -0
  117. package/dist/utils/natural-language.d.ts +111 -0
  118. package/dist/utils/natural-language.d.ts.map +1 -0
  119. package/dist/utils/natural-language.js +297 -0
  120. package/dist/utils/natural-language.js.map +1 -0
  121. package/dist/utils/natural-language.test.d.ts +2 -0
  122. package/dist/utils/natural-language.test.d.ts.map +1 -0
  123. package/dist/utils/natural-language.test.js +197 -0
  124. package/dist/utils/natural-language.test.js.map +1 -0
  125. package/dist/utils/priority-queue.d.ts +17 -0
  126. package/dist/utils/priority-queue.d.ts.map +1 -0
  127. package/dist/utils/priority-queue.js +62 -0
  128. package/dist/utils/priority-queue.js.map +1 -0
  129. package/dist/utils/priority-queue.test.d.ts +2 -0
  130. package/dist/utils/priority-queue.test.d.ts.map +1 -0
  131. package/dist/utils/priority-queue.test.js +82 -0
  132. package/dist/utils/priority-queue.test.js.map +1 -0
  133. package/dist/utils/projection.d.ts +65 -0
  134. package/dist/utils/projection.d.ts.map +1 -0
  135. package/dist/utils/projection.js +180 -0
  136. package/dist/utils/projection.js.map +1 -0
  137. package/dist/utils/projection.test.d.ts +2 -0
  138. package/dist/utils/projection.test.d.ts.map +1 -0
  139. package/dist/utils/projection.test.js +341 -0
  140. package/dist/utils/projection.test.js.map +1 -0
  141. package/dist/utils/terminal-ui.d.ts +208 -0
  142. package/dist/utils/terminal-ui.d.ts.map +1 -0
  143. package/dist/utils/terminal-ui.js +614 -0
  144. package/dist/utils/terminal-ui.js.map +1 -0
  145. package/dist/utils/terminal-ui.test.d.ts +2 -0
  146. package/dist/utils/terminal-ui.test.d.ts.map +1 -0
  147. package/dist/utils/terminal-ui.test.js +683 -0
  148. package/dist/utils/terminal-ui.test.js.map +1 -0
  149. package/dist/utils/workspace.d.ts +102 -0
  150. package/dist/utils/workspace.d.ts.map +1 -0
  151. package/dist/utils/workspace.js +183 -0
  152. package/dist/utils/workspace.js.map +1 -0
  153. package/dist/utils/workspace.test.d.ts +2 -0
  154. package/dist/utils/workspace.test.d.ts.map +1 -0
  155. package/dist/utils/workspace.test.js +97 -0
  156. package/dist/utils/workspace.test.js.map +1 -0
  157. package/package.json +5 -1
  158. package/src/algorithms/critical-path.test.ts +227 -0
  159. package/src/algorithms/critical-path.ts +14 -34
  160. package/src/algorithms/dependency-integrity.test.ts +335 -0
  161. package/src/algorithms/dependency-integrity.ts +4 -13
  162. package/src/algorithms/tech-analysis.test.ts +405 -0
  163. package/src/algorithms/tech-analysis.ts +27 -27
  164. package/src/algorithms/topological-sort.test.ts +182 -0
  165. package/src/algorithms/topological-sort.ts +6 -10
  166. package/src/schemas/index.ts +2 -13
  167. package/src/schemas/response-format.ts +6 -6
  168. package/src/schemas/response-schema.test.ts +314 -0
  169. package/src/schemas/response-schema.ts +25 -20
  170. package/src/schemas/task.ts +4 -22
  171. package/src/utils/dashboard-renderer.test.ts +976 -0
  172. package/src/utils/dashboard-renderer.ts +27 -59
  173. package/src/utils/date.test.ts +329 -0
  174. package/src/utils/date.ts +2 -10
  175. package/src/utils/hierarchy.test.ts +488 -0
  176. package/src/utils/hierarchy.ts +4 -5
  177. package/src/utils/id.test.ts +235 -0
  178. package/src/utils/index.ts +7 -1
  179. package/src/utils/natural-language.test.ts +234 -0
  180. package/src/utils/priority-queue.test.ts +103 -0
  181. package/src/utils/projection.test.ts +430 -0
  182. package/src/utils/terminal-ui.test.ts +831 -0
  183. package/src/utils/terminal-ui.ts +53 -54
  184. 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(task.id, deps.filter((depId) => taskMap.has(depId)));
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) {
@@ -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; // No dependencies or all satisfied
133
- blocked: number; // Has unsatisfied dependencies
134
- noDeps: number; // Tasks with no dependencies
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; // Why this task is recommended
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; // Total duration in minutes
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; // 0-100
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("Urgency/importance of response (default: medium)"),
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.array(ResponseOption).optional()
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
- .describe("Suggested options for user to choose from"),
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.array(z.string()).optional()
59
+ consequences: z
60
+ .array(z.string())
61
+ .optional()
59
62
  .describe("List of effects or changes that will occur"),
60
- defaultConfirm: z.boolean().optional()
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
- .describe("Action description (for confirmation type)"),
83
- consequences: z.array(z.string()).optional()
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
- .describe("Reasoning explanation (for suggestion type)"),
87
- defaultOption: z.string().optional()
88
- .describe("Default option value (for question type)"),
89
- defaultConfirm: z.boolean().optional()
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>;