@task-mcp/shared 1.0.3 → 1.0.6

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 (88) hide show
  1. package/dist/algorithms/critical-path.d.ts.map +1 -1
  2. package/dist/algorithms/critical-path.js +50 -26
  3. package/dist/algorithms/critical-path.js.map +1 -1
  4. package/dist/algorithms/dependency-integrity.d.ts +73 -0
  5. package/dist/algorithms/dependency-integrity.d.ts.map +1 -0
  6. package/dist/algorithms/dependency-integrity.js +189 -0
  7. package/dist/algorithms/dependency-integrity.js.map +1 -0
  8. package/dist/algorithms/index.d.ts +2 -0
  9. package/dist/algorithms/index.d.ts.map +1 -1
  10. package/dist/algorithms/index.js +2 -0
  11. package/dist/algorithms/index.js.map +1 -1
  12. package/dist/algorithms/tech-analysis.d.ts +106 -0
  13. package/dist/algorithms/tech-analysis.d.ts.map +1 -0
  14. package/dist/algorithms/tech-analysis.js +296 -0
  15. package/dist/algorithms/tech-analysis.js.map +1 -0
  16. package/dist/algorithms/tech-analysis.test.d.ts +2 -0
  17. package/dist/algorithms/tech-analysis.test.d.ts.map +1 -0
  18. package/dist/algorithms/tech-analysis.test.js +338 -0
  19. package/dist/algorithms/tech-analysis.test.js.map +1 -0
  20. package/dist/algorithms/topological-sort.d.ts.map +1 -1
  21. package/dist/algorithms/topological-sort.js +60 -8
  22. package/dist/algorithms/topological-sort.js.map +1 -1
  23. package/dist/schemas/inbox.d.ts +24 -0
  24. package/dist/schemas/inbox.d.ts.map +1 -0
  25. package/dist/schemas/inbox.js +25 -0
  26. package/dist/schemas/inbox.js.map +1 -0
  27. package/dist/schemas/index.d.ts +3 -1
  28. package/dist/schemas/index.d.ts.map +1 -1
  29. package/dist/schemas/index.js +9 -1
  30. package/dist/schemas/index.js.map +1 -1
  31. package/dist/schemas/response-format.d.ts +79 -0
  32. package/dist/schemas/response-format.d.ts.map +1 -0
  33. package/dist/schemas/response-format.js +17 -0
  34. package/dist/schemas/response-format.js.map +1 -0
  35. package/dist/schemas/task.d.ts +57 -0
  36. package/dist/schemas/task.d.ts.map +1 -1
  37. package/dist/schemas/task.js +34 -0
  38. package/dist/schemas/task.js.map +1 -1
  39. package/dist/utils/date.d.ts.map +1 -1
  40. package/dist/utils/date.js +17 -2
  41. package/dist/utils/date.js.map +1 -1
  42. package/dist/utils/hierarchy.d.ts +75 -0
  43. package/dist/utils/hierarchy.d.ts.map +1 -0
  44. package/dist/utils/hierarchy.js +179 -0
  45. package/dist/utils/hierarchy.js.map +1 -0
  46. package/dist/utils/id.d.ts +51 -1
  47. package/dist/utils/id.d.ts.map +1 -1
  48. package/dist/utils/id.js +124 -4
  49. package/dist/utils/id.js.map +1 -1
  50. package/dist/utils/id.test.d.ts +2 -0
  51. package/dist/utils/id.test.d.ts.map +1 -0
  52. package/dist/utils/id.test.js +228 -0
  53. package/dist/utils/id.test.js.map +1 -0
  54. package/dist/utils/index.d.ts +4 -2
  55. package/dist/utils/index.d.ts.map +1 -1
  56. package/dist/utils/index.js +7 -2
  57. package/dist/utils/index.js.map +1 -1
  58. package/dist/utils/natural-language.d.ts +45 -0
  59. package/dist/utils/natural-language.d.ts.map +1 -1
  60. package/dist/utils/natural-language.js +86 -0
  61. package/dist/utils/natural-language.js.map +1 -1
  62. package/dist/utils/projection.d.ts +65 -0
  63. package/dist/utils/projection.d.ts.map +1 -0
  64. package/dist/utils/projection.js +181 -0
  65. package/dist/utils/projection.js.map +1 -0
  66. package/dist/utils/projection.test.d.ts +2 -0
  67. package/dist/utils/projection.test.d.ts.map +1 -0
  68. package/dist/utils/projection.test.js +400 -0
  69. package/dist/utils/projection.test.js.map +1 -0
  70. package/package.json +1 -1
  71. package/src/algorithms/critical-path.ts +56 -24
  72. package/src/algorithms/dependency-integrity.ts +270 -0
  73. package/src/algorithms/index.ts +28 -0
  74. package/src/algorithms/tech-analysis.test.ts +413 -0
  75. package/src/algorithms/tech-analysis.ts +412 -0
  76. package/src/algorithms/topological-sort.ts +66 -9
  77. package/src/schemas/inbox.ts +32 -0
  78. package/src/schemas/index.ts +31 -0
  79. package/src/schemas/response-format.ts +108 -0
  80. package/src/schemas/task.ts +50 -0
  81. package/src/utils/date.ts +18 -2
  82. package/src/utils/hierarchy.ts +224 -0
  83. package/src/utils/id.test.ts +281 -0
  84. package/src/utils/id.ts +139 -4
  85. package/src/utils/index.ts +46 -2
  86. package/src/utils/natural-language.ts +113 -0
  87. package/src/utils/projection.test.ts +505 -0
  88. package/src/utils/projection.ts +251 -0
@@ -0,0 +1,412 @@
1
+ import type { Task, TechArea, RiskLevel } from "../schemas/task.js";
2
+
3
+ /**
4
+ * Tech area ordering rules (lower = execute first)
5
+ * Based on dependency flow: DB changes → Infrastructure → Backend → Frontend → Tests
6
+ */
7
+ const TECH_ORDER: Record<TechArea, number> = {
8
+ schema: 0, // DB/schema changes first
9
+ infra: 0, // Infrastructure setup
10
+ devops: 1, // CI/CD pipelines
11
+ backend: 2, // API/server
12
+ frontend: 3, // UI
13
+ test: 4, // Tests
14
+ docs: 4, // Documentation
15
+ refactor: 5, // Refactoring last
16
+ };
17
+
18
+ /**
19
+ * Risk level ordering (lower = safer, execute first)
20
+ */
21
+ const RISK_ORDER: Record<RiskLevel, number> = {
22
+ low: 0,
23
+ medium: 1,
24
+ high: 2,
25
+ critical: 3,
26
+ };
27
+
28
+ /**
29
+ * Result of safe order suggestion
30
+ */
31
+ export interface SafeOrderResult {
32
+ /** Tasks ordered for safe execution */
33
+ orderedTasks: Task[];
34
+ /** Grouping by phase/tech level */
35
+ phases: SafeOrderPhase[];
36
+ /** Summary statistics */
37
+ summary: {
38
+ totalTasks: number;
39
+ breakingChanges: number;
40
+ highRiskCount: number;
41
+ };
42
+ }
43
+
44
+ /**
45
+ * A phase in the safe execution order
46
+ */
47
+ export interface SafeOrderPhase {
48
+ /** Phase number (1-based) */
49
+ phase: number;
50
+ /** Primary tech area for this phase */
51
+ primaryArea: TechArea | "mixed";
52
+ /** Tasks in this phase */
53
+ tasks: Task[];
54
+ /** Notes about this phase */
55
+ notes: string[];
56
+ }
57
+
58
+ /**
59
+ * Get the minimum tech order value for a task
60
+ * If multiple areas, use the lowest (most foundational)
61
+ */
62
+ function getMinTechOrder(task: Task): number {
63
+ const areas = task.techStack?.areas ?? [];
64
+ if (areas.length === 0) return TECH_ORDER.backend; // Default to backend
65
+
66
+ return Math.min(...areas.map((area) => TECH_ORDER[area]));
67
+ }
68
+
69
+ /**
70
+ * Get the risk order value for a task
71
+ */
72
+ function getRiskOrder(task: Task): number {
73
+ const riskLevel = task.techStack?.riskLevel ?? "medium";
74
+ return RISK_ORDER[riskLevel];
75
+ }
76
+
77
+ /**
78
+ * Check if a task has breaking changes
79
+ */
80
+ function hasBreakingChange(task: Task): boolean {
81
+ return task.techStack?.hasBreakingChange === true;
82
+ }
83
+
84
+ /**
85
+ * Suggest safe execution order for tasks
86
+ *
87
+ * Ordering strategy:
88
+ * 1. Tech level (schema → infra → devops → backend → frontend → test → docs → refactor)
89
+ * 2. Risk level within same tech level (low → medium → high → critical)
90
+ * 3. Breaking changes last within same tech/risk level
91
+ * 4. Priority as tiebreaker (critical > high > medium > low)
92
+ */
93
+ export function suggestSafeOrder(tasks: Task[]): SafeOrderResult {
94
+ // Filter to active tasks only
95
+ const activeTasks = tasks.filter(
96
+ (t) => t.status === "pending" || t.status === "in_progress"
97
+ );
98
+
99
+ if (activeTasks.length === 0) {
100
+ return {
101
+ orderedTasks: [],
102
+ phases: [],
103
+ summary: {
104
+ totalTasks: 0,
105
+ breakingChanges: 0,
106
+ highRiskCount: 0,
107
+ },
108
+ };
109
+ }
110
+
111
+ // Sort tasks
112
+ const sorted = [...activeTasks].sort((a, b) => {
113
+ // 1. Tech level (lower first)
114
+ const techDiff = getMinTechOrder(a) - getMinTechOrder(b);
115
+ if (techDiff !== 0) return techDiff;
116
+
117
+ // 2. Risk level (lower first)
118
+ const riskDiff = getRiskOrder(a) - getRiskOrder(b);
119
+ if (riskDiff !== 0) return riskDiff;
120
+
121
+ // 3. Breaking changes last
122
+ const aBreaking = hasBreakingChange(a) ? 1 : 0;
123
+ const bBreaking = hasBreakingChange(b) ? 1 : 0;
124
+ if (aBreaking !== bBreaking) return aBreaking - bBreaking;
125
+
126
+ // 4. Priority as tiebreaker (higher priority first)
127
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
128
+ return (priorityOrder[a.priority] ?? 2) - (priorityOrder[b.priority] ?? 2);
129
+ });
130
+
131
+ // Group into phases by tech level
132
+ const phases: SafeOrderPhase[] = [];
133
+ let currentTechOrder = -1;
134
+ let currentPhase: SafeOrderPhase | null = null;
135
+
136
+ for (const task of sorted) {
137
+ const techOrder = getMinTechOrder(task);
138
+
139
+ if (techOrder !== currentTechOrder) {
140
+ // Start new phase
141
+ const primaryArea = getPrimaryArea(task);
142
+ currentPhase = {
143
+ phase: phases.length + 1,
144
+ primaryArea,
145
+ tasks: [task],
146
+ notes: [],
147
+ };
148
+ phases.push(currentPhase);
149
+ currentTechOrder = techOrder;
150
+ } else {
151
+ // Add to current phase
152
+ currentPhase!.tasks.push(task);
153
+ }
154
+ }
155
+
156
+ // Add notes to phases
157
+ for (const phase of phases) {
158
+ const breakingCount = phase.tasks.filter(hasBreakingChange).length;
159
+ const highRiskCount = phase.tasks.filter(
160
+ (t) => t.techStack?.riskLevel === "high" || t.techStack?.riskLevel === "critical"
161
+ ).length;
162
+
163
+ if (breakingCount > 0) {
164
+ phase.notes.push(`${breakingCount} breaking change(s) - test thoroughly`);
165
+ }
166
+ if (highRiskCount > 0) {
167
+ phase.notes.push(`${highRiskCount} high-risk task(s) - review carefully`);
168
+ }
169
+ }
170
+
171
+ // Calculate summary
172
+ const breakingChanges = sorted.filter(hasBreakingChange).length;
173
+ const highRiskCount = sorted.filter(
174
+ (t) => t.techStack?.riskLevel === "high" || t.techStack?.riskLevel === "critical"
175
+ ).length;
176
+
177
+ return {
178
+ orderedTasks: sorted,
179
+ phases,
180
+ summary: {
181
+ totalTasks: sorted.length,
182
+ breakingChanges,
183
+ highRiskCount,
184
+ },
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Get the primary tech area for a task
190
+ */
191
+ function getPrimaryArea(task: Task): TechArea | "mixed" {
192
+ const areas = task.techStack?.areas;
193
+ if (!areas || areas.length === 0) return "mixed";
194
+
195
+ // Return the one with lowest order (most foundational)
196
+ let primary: TechArea = areas[0]!;
197
+ for (let i = 1; i < areas.length; i++) {
198
+ const area = areas[i]!;
199
+ if (TECH_ORDER[area] < TECH_ORDER[primary]) {
200
+ primary = area;
201
+ }
202
+ }
203
+ return primary;
204
+ }
205
+
206
+ /**
207
+ * Find tasks with breaking changes
208
+ */
209
+ export function findBreakingChanges(tasks: Task[]): Task[] {
210
+ return tasks.filter(
211
+ (t) =>
212
+ (t.status === "pending" || t.status === "in_progress") &&
213
+ hasBreakingChange(t)
214
+ );
215
+ }
216
+
217
+ /**
218
+ * Find high-risk tasks (high or critical risk level)
219
+ */
220
+ export function findHighRiskTasks(tasks: Task[]): Task[] {
221
+ return tasks.filter(
222
+ (t) =>
223
+ (t.status === "pending" || t.status === "in_progress") &&
224
+ (t.techStack?.riskLevel === "high" || t.techStack?.riskLevel === "critical")
225
+ );
226
+ }
227
+
228
+ /**
229
+ * Group tasks by tech area
230
+ * A task may appear in multiple groups if it spans multiple areas
231
+ */
232
+ export function groupByTechArea(tasks: Task[]): Map<TechArea, Task[]> {
233
+ const groups = new Map<TechArea, Task[]>();
234
+
235
+ // Initialize all groups
236
+ const allAreas: TechArea[] = [
237
+ "schema", "infra", "devops", "backend", "frontend", "test", "docs", "refactor"
238
+ ];
239
+ for (const area of allAreas) {
240
+ groups.set(area, []);
241
+ }
242
+
243
+ // Group active tasks
244
+ const activeTasks = tasks.filter(
245
+ (t) => t.status === "pending" || t.status === "in_progress"
246
+ );
247
+
248
+ for (const task of activeTasks) {
249
+ const areas = task.techStack?.areas ?? [];
250
+ if (areas.length === 0) {
251
+ // Default to backend if no area specified
252
+ groups.get("backend")!.push(task);
253
+ } else {
254
+ for (const area of areas) {
255
+ groups.get(area)!.push(task);
256
+ }
257
+ }
258
+ }
259
+
260
+ return groups;
261
+ }
262
+
263
+ /**
264
+ * Complexity distribution by level
265
+ */
266
+ export interface ComplexityDistribution {
267
+ low: number; // 1-3
268
+ medium: number; // 4-6
269
+ high: number; // 7-10
270
+ }
271
+
272
+ /**
273
+ * Get complexity summary for a project
274
+ */
275
+ export interface ComplexitySummary {
276
+ /** Distribution of complexity scores */
277
+ distribution: ComplexityDistribution;
278
+ /** Tasks that should be broken down (score >= 7) */
279
+ needsBreakdown: Task[];
280
+ /** Average complexity score */
281
+ averageScore: number;
282
+ /** Tasks without complexity analysis */
283
+ unanalyzed: Task[];
284
+ }
285
+
286
+ /**
287
+ * Analyze complexity distribution across tasks
288
+ */
289
+ export function getComplexitySummary(tasks: Task[]): ComplexitySummary {
290
+ const activeTasks = tasks.filter(
291
+ (t) => t.status === "pending" || t.status === "in_progress"
292
+ );
293
+
294
+ const analyzed = activeTasks.filter((t) => t.complexity?.score !== undefined);
295
+ const unanalyzed = activeTasks.filter((t) => t.complexity?.score === undefined);
296
+
297
+ // Calculate distribution
298
+ const distribution: ComplexityDistribution = {
299
+ low: 0,
300
+ medium: 0,
301
+ high: 0,
302
+ };
303
+
304
+ let totalScore = 0;
305
+ const needsBreakdown: Task[] = [];
306
+
307
+ for (const task of analyzed) {
308
+ const score = task.complexity!.score!;
309
+ totalScore += score;
310
+
311
+ if (score <= 3) {
312
+ distribution.low++;
313
+ } else if (score <= 6) {
314
+ distribution.medium++;
315
+ } else {
316
+ distribution.high++;
317
+ needsBreakdown.push(task);
318
+ }
319
+ }
320
+
321
+ return {
322
+ distribution,
323
+ needsBreakdown,
324
+ averageScore: analyzed.length > 0 ? totalScore / analyzed.length : 0,
325
+ unanalyzed,
326
+ };
327
+ }
328
+
329
+ /**
330
+ * Get tech stack summary for a project
331
+ */
332
+ export interface TechStackSummary {
333
+ /** Count of tasks per tech area */
334
+ areaCounts: Record<TechArea, number>;
335
+ /** Tasks with breaking changes */
336
+ breakingChanges: Task[];
337
+ /** Risk distribution */
338
+ riskDistribution: Record<RiskLevel, number>;
339
+ /** Tasks without tech stack analysis */
340
+ unanalyzed: Task[];
341
+ }
342
+
343
+ /**
344
+ * Analyze tech stack distribution across tasks
345
+ */
346
+ export function getTechStackSummary(tasks: Task[]): TechStackSummary {
347
+ const activeTasks = tasks.filter(
348
+ (t) => t.status === "pending" || t.status === "in_progress"
349
+ );
350
+
351
+ const analyzed = activeTasks.filter((t) => t.techStack?.areas !== undefined);
352
+ const unanalyzed = activeTasks.filter((t) => t.techStack?.areas === undefined);
353
+
354
+ // Count by area
355
+ const areaCounts: Record<TechArea, number> = {
356
+ schema: 0,
357
+ infra: 0,
358
+ devops: 0,
359
+ backend: 0,
360
+ frontend: 0,
361
+ test: 0,
362
+ docs: 0,
363
+ refactor: 0,
364
+ };
365
+
366
+ for (const task of analyzed) {
367
+ for (const area of task.techStack!.areas!) {
368
+ areaCounts[area]++;
369
+ }
370
+ }
371
+
372
+ // Risk distribution
373
+ const riskDistribution: Record<RiskLevel, number> = {
374
+ low: 0,
375
+ medium: 0,
376
+ high: 0,
377
+ critical: 0,
378
+ };
379
+
380
+ for (const task of analyzed) {
381
+ const risk = task.techStack?.riskLevel ?? "medium";
382
+ riskDistribution[risk]++;
383
+ }
384
+
385
+ // Breaking changes
386
+ const breakingChanges = findBreakingChanges(activeTasks);
387
+
388
+ return {
389
+ areaCounts,
390
+ breakingChanges,
391
+ riskDistribution,
392
+ unanalyzed,
393
+ };
394
+ }
395
+
396
+ /**
397
+ * Suggest number of subtasks based on complexity score
398
+ *
399
+ * Mapping:
400
+ * 1-2: 0 (no breakdown needed)
401
+ * 3-4: 2
402
+ * 5-6: 3-4
403
+ * 7-8: 5-6
404
+ * 9-10: 7-10
405
+ */
406
+ export function suggestSubtaskCount(score: number): number {
407
+ if (score <= 2) return 0;
408
+ if (score <= 4) return 2;
409
+ if (score <= 6) return Math.ceil((score - 4) * 0.5 + 3); // 3-4
410
+ if (score <= 8) return Math.ceil((score - 6) * 0.5 + 5); // 5-6
411
+ return Math.ceil((score - 8) * 1.5 + 7); // 7-10
412
+ }
@@ -10,6 +10,67 @@ export interface TaskNode {
10
10
  estimate: number; // Duration in minutes
11
11
  }
12
12
 
13
+ /**
14
+ * Max-heap implementation for priority queue (higher priority = higher value comes first)
15
+ * O(log n) insert and extract operations vs O(n log n) for sort-based approach
16
+ */
17
+ class PriorityQueue<T> {
18
+ private heap: T[] = [];
19
+ private compare: (a: T, b: T) => number;
20
+
21
+ constructor(compare: (a: T, b: T) => number) {
22
+ this.compare = compare;
23
+ }
24
+
25
+ get length(): number {
26
+ return this.heap.length;
27
+ }
28
+
29
+ push(item: T): void {
30
+ this.heap.push(item);
31
+ this.bubbleUp(this.heap.length - 1);
32
+ }
33
+
34
+ pop(): T | undefined {
35
+ if (this.heap.length === 0) return undefined;
36
+ if (this.heap.length === 1) return this.heap.pop();
37
+
38
+ const result = this.heap[0];
39
+ this.heap[0] = this.heap.pop()!;
40
+ this.bubbleDown(0);
41
+ return result;
42
+ }
43
+
44
+ private bubbleUp(index: number): void {
45
+ while (index > 0) {
46
+ const parentIndex = Math.floor((index - 1) / 2);
47
+ if (this.compare(this.heap[index]!, this.heap[parentIndex]!) <= 0) break;
48
+ [this.heap[index], this.heap[parentIndex]] = [this.heap[parentIndex]!, this.heap[index]!];
49
+ index = parentIndex;
50
+ }
51
+ }
52
+
53
+ private bubbleDown(index: number): void {
54
+ const length = this.heap.length;
55
+ while (true) {
56
+ const leftChild = 2 * index + 1;
57
+ const rightChild = 2 * index + 2;
58
+ let largest = index;
59
+
60
+ if (leftChild < length && this.compare(this.heap[leftChild]!, this.heap[largest]!) > 0) {
61
+ largest = leftChild;
62
+ }
63
+ if (rightChild < length && this.compare(this.heap[rightChild]!, this.heap[largest]!) > 0) {
64
+ largest = rightChild;
65
+ }
66
+
67
+ if (largest === index) break;
68
+ [this.heap[index], this.heap[largest]] = [this.heap[largest]!, this.heap[index]!];
69
+ index = largest;
70
+ }
71
+ }
72
+ }
73
+
13
74
  /**
14
75
  * Convert priority string to numeric value
15
76
  */
@@ -71,21 +132,19 @@ export function topologicalSort(tasks: Task[]): Task[] {
71
132
  }
72
133
  }
73
134
 
74
- // Initialize queue with nodes that have no dependencies
75
- const queue: TaskNode[] = [];
135
+ // Initialize priority queue with nodes that have no dependencies
136
+ // Using max-heap: higher priority comes out first
137
+ const queue = new PriorityQueue<TaskNode>((a, b) => a.priority - b.priority);
76
138
  for (const node of nodes) {
77
139
  if (inDegree.get(node.id) === 0) {
78
140
  queue.push(node);
79
141
  }
80
142
  }
81
143
 
82
- // Sort by priority (higher first)
83
- queue.sort((a, b) => b.priority - a.priority);
84
-
85
144
  const result: Task[] = [];
86
145
 
87
146
  while (queue.length > 0) {
88
- const current = queue.shift()!;
147
+ const current = queue.pop()!;
89
148
  const task = taskMap.get(current.id);
90
149
  if (task) {
91
150
  result.push(task);
@@ -98,9 +157,7 @@ export function topologicalSort(tasks: Task[]): Task[] {
98
157
 
99
158
  if (newDegree === 0) {
100
159
  const neighborNode = nodeMap.get(neighborId)!;
101
- queue.push(neighborNode);
102
- // Re-sort to maintain priority order
103
- queue.sort((a, b) => b.priority - a.priority);
160
+ queue.push(neighborNode); // O(log n) insertion maintains heap property
104
161
  }
105
162
  }
106
163
  }
@@ -0,0 +1,32 @@
1
+ import { type } from "arktype";
2
+
3
+ // Inbox item status
4
+ export const InboxStatus = type("'pending' | 'promoted' | 'discarded'");
5
+ export type InboxStatus = typeof InboxStatus.infer;
6
+
7
+ // Inbox item schema - lightweight idea/memo capture
8
+ export const InboxItem = type({
9
+ id: "string",
10
+ content: "string", // The memo/idea content
11
+ capturedAt: "string", // ISO timestamp
12
+ "source?": "string", // Origin: 'cli', 'mcp', 'api', etc.
13
+ "tags?": "string[]", // Simple tags for organization
14
+ "promotedToTaskId?": "string", // Task ID if promoted
15
+ status: InboxStatus,
16
+ });
17
+ export type InboxItem = typeof InboxItem.infer;
18
+
19
+ // Inbox item creation input (minimal)
20
+ export const InboxCreateInput = type({
21
+ content: "string",
22
+ "source?": "string",
23
+ "tags?": "string[]",
24
+ });
25
+ export type InboxCreateInput = typeof InboxCreateInput.infer;
26
+
27
+ // Inbox item update input
28
+ export const InboxUpdateInput = type({
29
+ "content?": "string",
30
+ "tags?": "string[]",
31
+ });
32
+ export type InboxUpdateInput = typeof InboxUpdateInput.infer;
@@ -6,6 +6,13 @@ export {
6
6
  Dependency,
7
7
  TimeEstimate,
8
8
  Recurrence,
9
+ // Analysis schemas
10
+ ComplexityFactor,
11
+ ComplexityAnalysis,
12
+ TechArea,
13
+ RiskLevel,
14
+ TechStackAnalysis,
15
+ // Core schemas
9
16
  Task,
10
17
  TaskCreateInput,
11
18
  TaskUpdateInput,
@@ -28,3 +35,27 @@ export {
28
35
  SmartView,
29
36
  BuiltInView,
30
37
  } from "./view.js";
38
+
39
+ // Inbox schemas
40
+ export {
41
+ InboxStatus,
42
+ InboxItem,
43
+ InboxCreateInput,
44
+ InboxUpdateInput,
45
+ } from "./inbox.js";
46
+
47
+ // Response format schemas (token optimization)
48
+ export {
49
+ ResponseFormat,
50
+ DEFAULT_LIMIT,
51
+ MAX_LIMIT,
52
+ type PaginatedResponse,
53
+ type TaskSummary,
54
+ type TaskPreview,
55
+ type ProjectSummary,
56
+ type ProjectPreview,
57
+ type InboxSummary,
58
+ type InboxPreview,
59
+ type CriticalPathSummary,
60
+ type BottleneckSummary,
61
+ } from "./response-format.js";
@@ -0,0 +1,108 @@
1
+ import { type } from "arktype";
2
+
3
+ /**
4
+ * Response Format Schema
5
+ *
6
+ * Token-efficient response formats for MCP tools.
7
+ * Based on Anthropic's recommended patterns for reducing token usage.
8
+ *
9
+ * - concise: Minimal fields (4-6), JSON format for machine processing
10
+ * - standard: Common fields (7-10), balanced for most use cases
11
+ * - detailed: Full object, human-readable format
12
+ */
13
+
14
+ // Response format options
15
+ export const ResponseFormat = type("'concise' | 'standard' | 'detailed'");
16
+ export type ResponseFormat = typeof ResponseFormat.infer;
17
+
18
+ // Default limits for pagination
19
+ export const DEFAULT_LIMIT = 20;
20
+ export const MAX_LIMIT = 100;
21
+
22
+ /**
23
+ * Paginated response wrapper
24
+ */
25
+ export interface PaginatedResponse<T> {
26
+ items: T[];
27
+ total: number;
28
+ limit: number;
29
+ offset: number;
30
+ hasMore: boolean;
31
+ }
32
+
33
+ /**
34
+ * Task projection types - progressively more detailed
35
+ */
36
+
37
+ // Concise: 4 essential fields (~30 tokens per task)
38
+ export interface TaskSummary {
39
+ id: string;
40
+ title: string;
41
+ status: string;
42
+ priority: string;
43
+ }
44
+
45
+ // Standard: 8 common fields (~60 tokens per task)
46
+ export interface TaskPreview extends TaskSummary {
47
+ dueDate?: string;
48
+ tags?: string[];
49
+ contexts?: string[];
50
+ parentId?: string;
51
+ }
52
+
53
+ // Detailed: Full Task object (~200+ tokens per task)
54
+ // Use the full Task type from task.ts
55
+
56
+ /**
57
+ * Project projection types
58
+ */
59
+
60
+ // Concise: 4 essential fields
61
+ export interface ProjectSummary {
62
+ id: string;
63
+ name: string;
64
+ status: string;
65
+ completionPercentage?: number;
66
+ }
67
+
68
+ // Standard: 7 common fields
69
+ export interface ProjectPreview extends ProjectSummary {
70
+ description?: string;
71
+ totalTasks?: number;
72
+ completedTasks?: number;
73
+ }
74
+
75
+ /**
76
+ * Inbox projection types
77
+ */
78
+
79
+ // Concise: 3 essential fields
80
+ export interface InboxSummary {
81
+ id: string;
82
+ content: string;
83
+ status: string;
84
+ }
85
+
86
+ // Standard: 5 common fields
87
+ export interface InboxPreview extends InboxSummary {
88
+ capturedAt: string;
89
+ tags?: string[];
90
+ }
91
+
92
+ /**
93
+ * Analysis result types - optimized for token efficiency
94
+ */
95
+
96
+ // Critical path summary (concise format)
97
+ export interface CriticalPathSummary {
98
+ totalDuration: number;
99
+ taskCount: number;
100
+ taskIds: string[];
101
+ }
102
+
103
+ // Bottleneck summary (concise format)
104
+ export interface BottleneckSummary {
105
+ taskId: string;
106
+ title: string;
107
+ blockedCount: number;
108
+ }