@task-mcp/shared 1.0.27 → 1.0.29

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 (69) hide show
  1. package/dist/algorithms/index.d.ts +1 -1
  2. package/dist/algorithms/index.d.ts.map +1 -1
  3. package/dist/algorithms/index.js +1 -1
  4. package/dist/algorithms/index.js.map +1 -1
  5. package/dist/algorithms/topological-sort.d.ts +21 -1
  6. package/dist/algorithms/topological-sort.d.ts.map +1 -1
  7. package/dist/algorithms/topological-sort.js +12 -1
  8. package/dist/algorithms/topological-sort.js.map +1 -1
  9. package/dist/schemas/index.d.ts +3 -2
  10. package/dist/schemas/index.d.ts.map +1 -1
  11. package/dist/schemas/index.js +4 -0
  12. package/dist/schemas/index.js.map +1 -1
  13. package/dist/schemas/response-format.d.ts +8 -0
  14. package/dist/schemas/response-format.d.ts.map +1 -1
  15. package/dist/schemas/response-format.js.map +1 -1
  16. package/dist/schemas/session.d.ts +521 -0
  17. package/dist/schemas/session.d.ts.map +1 -0
  18. package/dist/schemas/session.js +79 -0
  19. package/dist/schemas/session.js.map +1 -0
  20. package/dist/schemas/task.d.ts +285 -0
  21. package/dist/schemas/task.d.ts.map +1 -1
  22. package/dist/schemas/task.js +64 -6
  23. package/dist/schemas/task.js.map +1 -1
  24. package/dist/schemas/view.d.ts +18 -18
  25. package/dist/schemas/work-context.test.d.ts +2 -0
  26. package/dist/schemas/work-context.test.d.ts.map +1 -0
  27. package/dist/schemas/work-context.test.js +192 -0
  28. package/dist/schemas/work-context.test.js.map +1 -0
  29. package/dist/utils/hierarchy.d.ts.map +1 -1
  30. package/dist/utils/hierarchy.js +6 -3
  31. package/dist/utils/hierarchy.js.map +1 -1
  32. package/dist/utils/index.d.ts +2 -1
  33. package/dist/utils/index.d.ts.map +1 -1
  34. package/dist/utils/index.js +17 -1
  35. package/dist/utils/index.js.map +1 -1
  36. package/dist/utils/natural-language.d.ts.map +1 -1
  37. package/dist/utils/natural-language.js +54 -2
  38. package/dist/utils/natural-language.js.map +1 -1
  39. package/dist/utils/natural-language.test.js +46 -0
  40. package/dist/utils/natural-language.test.js.map +1 -1
  41. package/dist/utils/plan-parser.d.ts +57 -0
  42. package/dist/utils/plan-parser.d.ts.map +1 -0
  43. package/dist/utils/plan-parser.js +377 -0
  44. package/dist/utils/plan-parser.js.map +1 -0
  45. package/dist/utils/projection.d.ts.map +1 -1
  46. package/dist/utils/projection.js +29 -8
  47. package/dist/utils/projection.js.map +1 -1
  48. package/dist/utils/terminal-ui.d.ts +129 -0
  49. package/dist/utils/terminal-ui.d.ts.map +1 -1
  50. package/dist/utils/terminal-ui.js +191 -0
  51. package/dist/utils/terminal-ui.js.map +1 -1
  52. package/dist/utils/terminal-ui.test.js +236 -0
  53. package/dist/utils/terminal-ui.test.js.map +1 -1
  54. package/package.json +2 -2
  55. package/src/algorithms/index.ts +3 -0
  56. package/src/algorithms/topological-sort.ts +31 -1
  57. package/src/schemas/index.ts +16 -0
  58. package/src/schemas/response-format.ts +13 -1
  59. package/src/schemas/session.ts +100 -0
  60. package/src/schemas/task.ts +85 -16
  61. package/src/schemas/work-context.test.ts +221 -0
  62. package/src/utils/hierarchy.ts +8 -3
  63. package/src/utils/index.ts +31 -0
  64. package/src/utils/natural-language.test.ts +56 -0
  65. package/src/utils/natural-language.ts +60 -3
  66. package/src/utils/plan-parser.ts +478 -0
  67. package/src/utils/projection.ts +29 -8
  68. package/src/utils/terminal-ui.test.ts +286 -0
  69. package/src/utils/terminal-ui.ts +315 -0
@@ -0,0 +1,221 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { WorkContext, WorkPatterns, QualityLevel } from "./task.js";
3
+
4
+ describe("WorkContext Schema", () => {
5
+ describe("QualityLevel", () => {
6
+ it("should accept valid quality levels", () => {
7
+ expect(() => QualityLevel.parse("quick")).not.toThrow();
8
+ expect(() => QualityLevel.parse("standard")).not.toThrow();
9
+ expect(() => QualityLevel.parse("thorough")).not.toThrow();
10
+ expect(() => QualityLevel.parse("exhaustive")).not.toThrow();
11
+ });
12
+
13
+ it("should reject invalid quality levels", () => {
14
+ expect(() => QualityLevel.parse("fast")).toThrow();
15
+ expect(() => QualityLevel.parse("slow")).toThrow();
16
+ expect(() => QualityLevel.parse("normal")).toThrow();
17
+ });
18
+ });
19
+
20
+ describe("WorkPatterns", () => {
21
+ it("should validate work patterns with all fields", () => {
22
+ const patterns = {
23
+ parallelExecution: true,
24
+ fileByFileAnalysis: true,
25
+ testFirst: true,
26
+ reviewRequired: true,
27
+ };
28
+
29
+ expect(() => WorkPatterns.parse(patterns)).not.toThrow();
30
+ const parsed = WorkPatterns.parse(patterns);
31
+ expect(parsed.parallelExecution).toBe(true);
32
+ expect(parsed.fileByFileAnalysis).toBe(true);
33
+ expect(parsed.testFirst).toBe(true);
34
+ expect(parsed.reviewRequired).toBe(true);
35
+ });
36
+
37
+ it("should validate work patterns with partial fields", () => {
38
+ const patterns = {
39
+ testFirst: true,
40
+ };
41
+
42
+ expect(() => WorkPatterns.parse(patterns)).not.toThrow();
43
+ const parsed = WorkPatterns.parse(patterns);
44
+ expect(parsed.testFirst).toBe(true);
45
+ expect(parsed.parallelExecution).toBeUndefined();
46
+ });
47
+
48
+ it("should validate empty work patterns", () => {
49
+ expect(() => WorkPatterns.parse({})).not.toThrow();
50
+ });
51
+ });
52
+
53
+ describe("WorkContext", () => {
54
+ it("should validate full work context", () => {
55
+ const context = {
56
+ intent: "Migrate API layer to service pattern",
57
+ methodology: "Analyze each file -> Review types -> Extract service -> Test",
58
+ qualityLevel: "thorough" as const,
59
+ patterns: {
60
+ fileByFileAnalysis: true,
61
+ reviewRequired: true,
62
+ },
63
+ acceptanceCriteria: ["All tests pass", "No type errors", "100% coverage"],
64
+ };
65
+
66
+ expect(() => WorkContext.parse(context)).not.toThrow();
67
+ const parsed = WorkContext.parse(context);
68
+ expect(parsed.intent).toBe("Migrate API layer to service pattern");
69
+ expect(parsed.methodology).toContain("Analyze each file");
70
+ expect(parsed.qualityLevel).toBe("thorough");
71
+ expect(parsed.patterns?.fileByFileAnalysis).toBe(true);
72
+ expect(parsed.acceptanceCriteria).toHaveLength(3);
73
+ });
74
+
75
+ it("should validate work context with only intent", () => {
76
+ const context = {
77
+ intent: "Quick bug fix",
78
+ };
79
+
80
+ expect(() => WorkContext.parse(context)).not.toThrow();
81
+ const parsed = WorkContext.parse(context);
82
+ expect(parsed.intent).toBe("Quick bug fix");
83
+ expect(parsed.methodology).toBeUndefined();
84
+ });
85
+
86
+ it("should validate work context with only quality level", () => {
87
+ const context = {
88
+ qualityLevel: "quick" as const,
89
+ };
90
+
91
+ expect(() => WorkContext.parse(context)).not.toThrow();
92
+ const parsed = WorkContext.parse(context);
93
+ expect(parsed.qualityLevel).toBe("quick");
94
+ });
95
+
96
+ it("should validate work context with only patterns", () => {
97
+ const context = {
98
+ patterns: {
99
+ parallelExecution: true,
100
+ testFirst: true,
101
+ },
102
+ };
103
+
104
+ expect(() => WorkContext.parse(context)).not.toThrow();
105
+ const parsed = WorkContext.parse(context);
106
+ expect(parsed.patterns?.parallelExecution).toBe(true);
107
+ expect(parsed.patterns?.testFirst).toBe(true);
108
+ });
109
+
110
+ it("should validate work context with only acceptance criteria", () => {
111
+ const context = {
112
+ acceptanceCriteria: ["No critical bugs", "All edge cases handled"],
113
+ };
114
+
115
+ expect(() => WorkContext.parse(context)).not.toThrow();
116
+ const parsed = WorkContext.parse(context);
117
+ expect(parsed.acceptanceCriteria).toHaveLength(2);
118
+ expect(parsed.acceptanceCriteria?.[0]).toBe("No critical bugs");
119
+ });
120
+
121
+ it("should validate empty work context", () => {
122
+ expect(() => WorkContext.parse({})).not.toThrow();
123
+ });
124
+
125
+ it("should reject invalid quality level in work context", () => {
126
+ const context = {
127
+ qualityLevel: "invalid" as const,
128
+ };
129
+
130
+ expect(() => WorkContext.parse(context)).toThrow();
131
+ });
132
+
133
+ it("should allow empty acceptance criteria array", () => {
134
+ const context = {
135
+ acceptanceCriteria: [],
136
+ };
137
+
138
+ expect(() => WorkContext.parse(context)).not.toThrow();
139
+ const parsed = WorkContext.parse(context);
140
+ expect(parsed.acceptanceCriteria).toHaveLength(0);
141
+ });
142
+ });
143
+
144
+ describe("WorkContext use cases", () => {
145
+ it("should represent thorough code review context", () => {
146
+ const codeReviewContext = {
147
+ intent: "Review PR for security vulnerabilities and code quality",
148
+ methodology: "Check security -> Review performance -> Verify tests -> Check style",
149
+ qualityLevel: "thorough" as const,
150
+ patterns: {
151
+ fileByFileAnalysis: true,
152
+ reviewRequired: true,
153
+ },
154
+ acceptanceCriteria: [
155
+ "No security vulnerabilities",
156
+ "All tests pass",
157
+ "Code follows style guide",
158
+ ],
159
+ };
160
+
161
+ const parsed = WorkContext.parse(codeReviewContext);
162
+ expect(parsed.qualityLevel).toBe("thorough");
163
+ expect(parsed.patterns?.fileByFileAnalysis).toBe(true);
164
+ });
165
+
166
+ it("should represent quick bug fix context", () => {
167
+ const bugFixContext = {
168
+ intent: "Fix null pointer exception in user service",
169
+ qualityLevel: "quick" as const,
170
+ acceptanceCriteria: ["Bug no longer occurs", "Existing tests pass"],
171
+ };
172
+
173
+ const parsed = WorkContext.parse(bugFixContext);
174
+ expect(parsed.qualityLevel).toBe("quick");
175
+ expect(parsed.methodology).toBeUndefined();
176
+ expect(parsed.patterns).toBeUndefined();
177
+ });
178
+
179
+ it("should represent exhaustive security audit context", () => {
180
+ const securityAuditContext = {
181
+ intent: "Comprehensive security audit of authentication system",
182
+ methodology: "OWASP Top 10 -> Dependency scan -> Penetration test -> Report",
183
+ qualityLevel: "exhaustive" as const,
184
+ patterns: {
185
+ parallelExecution: true,
186
+ fileByFileAnalysis: true,
187
+ reviewRequired: true,
188
+ },
189
+ acceptanceCriteria: [
190
+ "No critical vulnerabilities",
191
+ "No high vulnerabilities",
192
+ "All dependencies up to date",
193
+ "Security report generated",
194
+ "Remediation plan created",
195
+ ],
196
+ };
197
+
198
+ const parsed = WorkContext.parse(securityAuditContext);
199
+ expect(parsed.qualityLevel).toBe("exhaustive");
200
+ expect(parsed.patterns?.parallelExecution).toBe(true);
201
+ expect(parsed.acceptanceCriteria).toHaveLength(5);
202
+ });
203
+
204
+ it("should represent TDD development context", () => {
205
+ const tddContext = {
206
+ intent: "Implement new feature using test-driven development",
207
+ methodology: "Write failing test -> Implement -> Refactor -> Repeat",
208
+ qualityLevel: "standard" as const,
209
+ patterns: {
210
+ testFirst: true,
211
+ reviewRequired: true,
212
+ },
213
+ acceptanceCriteria: ["All tests pass", "Coverage > 80%"],
214
+ };
215
+
216
+ const parsed = WorkContext.parse(tddContext);
217
+ expect(parsed.patterns?.testFirst).toBe(true);
218
+ expect(parsed.qualityLevel).toBe("standard");
219
+ });
220
+ });
221
+ });
@@ -217,13 +217,18 @@ export function buildTaskTree(
217
217
  return { task, children: [] };
218
218
  }
219
219
 
220
- const newVisited = new Set(visited);
221
- newVisited.add(task.id);
220
+ // Mark as visited (O(1) instead of copying the Set)
221
+ visited.add(task.id);
222
222
 
223
223
  const children = childrenMap.get(task.id) || [];
224
+ const childNodes = children.map((child) => buildNode(child, depth + 1, visited));
225
+
226
+ // Restore visited state after processing children (backtracking)
227
+ visited.delete(task.id);
228
+
224
229
  return {
225
230
  task,
226
- children: children.map((child) => buildNode(child, depth + 1, newVisited)),
231
+ children: childNodes,
227
232
  };
228
233
  }
229
234
 
@@ -127,6 +127,29 @@ export {
127
127
  formatDependencies,
128
128
  // Banner
129
129
  banner,
130
+ // Status symbols (checkbox style)
131
+ STATUS_SYMBOLS,
132
+ formatStatusSymbol,
133
+ // Priority badge (P0-P3 style)
134
+ PRIORITY_LEVELS,
135
+ formatPriorityBadge,
136
+ // Section renderer (GitHub CLI style)
137
+ renderSection,
138
+ renderSections,
139
+ type SectionItem,
140
+ type SectionOptions,
141
+ // Alert box
142
+ renderAlert,
143
+ type AlertSeverity,
144
+ type AlertOptions,
145
+ // Stats row
146
+ renderStats,
147
+ type StatItem,
148
+ // Due date formatter
149
+ formatDueDate,
150
+ // Task line formatter
151
+ formatTaskLine,
152
+ type TaskLineOptions,
130
153
  } from "./terminal-ui.js";
131
154
 
132
155
  // Workspace detection
@@ -140,3 +163,11 @@ export {
140
163
  detectWorkspace,
141
164
  detectWorkspaceSync,
142
165
  } from "./workspace.js";
166
+
167
+ // Plan parser
168
+ export {
169
+ parsePlan,
170
+ type ParsedPlanTask,
171
+ type ParsePlanOptions,
172
+ type ParsePlanResult,
173
+ } from "./plan-parser.js";
@@ -200,6 +200,62 @@ describe("parseTaskInput", () => {
200
200
  expect(() => parseTaskInput(" ")).toThrow(InputValidationError);
201
201
  });
202
202
  });
203
+
204
+ describe("workContext parsing", () => {
205
+ test("parses intent with :: separator", () => {
206
+ const result = parseTaskInput("API 리팩토링 :: 서비스 레이어 분리 !high");
207
+ expect(result.title).toBe("API 리팩토링");
208
+ expect(result.workContext?.intent).toBe("서비스 레이어 분리");
209
+ expect(result.priority).toBe("high");
210
+ });
211
+
212
+ test("parses qualityLevel with @thorough", () => {
213
+ const result = parseTaskInput("코드 리뷰 @thorough #backend");
214
+ expect(result.title).toBe("코드 리뷰");
215
+ expect(result.workContext?.qualityLevel).toBe("thorough");
216
+ expect(result.tags).toEqual(["backend"]);
217
+ });
218
+
219
+ test("parses qualityLevel with @quick", () => {
220
+ const result = parseTaskInput("버그 수정 @quick !high");
221
+ expect(result.title).toBe("버그 수정");
222
+ expect(result.workContext?.qualityLevel).toBe("quick");
223
+ });
224
+
225
+ test("parses Korean qualityLevel @꼼꼼히", () => {
226
+ const result = parseTaskInput("보안 점검 @꼼꼼히");
227
+ expect(result.workContext?.qualityLevel).toBe("thorough");
228
+ });
229
+
230
+ test("parses acceptanceCriteria with done:", () => {
231
+ const result = parseTaskInput("테스트 작성 done:테스트통과,커버리지80%");
232
+ expect(result.title).toBe("테스트 작성");
233
+ expect(result.workContext?.acceptanceCriteria).toEqual(["테스트통과", "커버리지80%"]);
234
+ });
235
+
236
+ test("parses full workContext", () => {
237
+ const result = parseTaskInput(
238
+ "API 마이그레이션 :: 서비스 분리 @thorough done:테스트통과,타입에러없음 !high #backend"
239
+ );
240
+ expect(result.title).toBe("API 마이그레이션");
241
+ expect(result.workContext?.intent).toBe("서비스 분리");
242
+ expect(result.workContext?.qualityLevel).toBe("thorough");
243
+ expect(result.workContext?.acceptanceCriteria).toEqual(["테스트통과", "타입에러없음"]);
244
+ expect(result.priority).toBe("high");
245
+ expect(result.tags).toEqual(["backend"]);
246
+ });
247
+
248
+ test("distinguishes qualityLevel from regular context", () => {
249
+ const result = parseTaskInput("작업 @thorough @focus @review");
250
+ expect(result.workContext?.qualityLevel).toBe("thorough");
251
+ expect(result.contexts).toEqual(["focus", "review"]);
252
+ });
253
+
254
+ test("no workContext when not specified", () => {
255
+ const result = parseTaskInput("간단한 작업 !medium #task");
256
+ expect(result.workContext).toBeUndefined();
257
+ });
258
+ });
203
259
  });
204
260
 
205
261
  describe("parseInboxInput", () => {
@@ -1,7 +1,25 @@
1
- import type { Priority, TaskCreateInput } from "../schemas/task.js";
1
+ import type { Priority, TaskCreateInput, QualityLevel, WorkContext } from "../schemas/task.js";
2
2
  import type { InboxCreateInput } from "../schemas/inbox.js";
3
3
  import { parseRelativeDate, formatDate } from "./date.js";
4
4
 
5
+ /**
6
+ * Quality level keywords for workContext parsing
7
+ */
8
+ const QUALITY_LEVEL_KEYWORDS: Record<string, QualityLevel> = {
9
+ quick: "quick",
10
+ 빠르게: "quick",
11
+ 간단히: "quick",
12
+ standard: "standard",
13
+ 표준: "standard",
14
+ 기본: "standard",
15
+ thorough: "thorough",
16
+ 꼼꼼히: "thorough",
17
+ 철저히: "thorough",
18
+ exhaustive: "exhaustive",
19
+ 완전히: "exhaustive",
20
+ 철저하게: "exhaustive",
21
+ };
22
+
5
23
  /**
6
24
  * Maximum allowed input length for natural language parsing.
7
25
  * Prevents DoS attacks from extremely long input strings.
@@ -296,11 +314,45 @@ export function parseTaskInput(input: string): TaskCreateInput {
296
314
  validateInputLength(input);
297
315
  let remaining = input.trim();
298
316
  const result: TaskCreateInput = { title: "" };
317
+ const workContext: Partial<WorkContext> = {};
318
+
319
+ // Extract intent first (:: separator)
320
+ // Format: "Task title :: intent description"
321
+ const intentMatch = remaining.match(/\s*::\s*(.+?)(?=\s+[@#!~^]|$)/);
322
+ if (intentMatch) {
323
+ workContext.intent = intentMatch[1]!.trim();
324
+ remaining = remaining.replace(intentMatch[0], " ").trim();
325
+ }
326
+
327
+ // Extract acceptanceCriteria (done:xxx,yyy or done:xxx)
328
+ // Format: "done:criterion1,criterion2" or "done:single criterion"
329
+ const doneMatch = remaining.match(/done:([^\s@#!~^]+(?:,[^\s@#!~^]+)*)/i);
330
+ if (doneMatch) {
331
+ workContext.acceptanceCriteria = doneMatch[1]!.split(",").map((c) => c.trim());
332
+ remaining = remaining.replace(doneMatch[0], " ").trim();
333
+ }
334
+
335
+ // Extract qualityLevel (@quick, @thorough, etc.) - before contexts
336
+ // These are reserved keywords that become qualityLevel, not contexts
337
+ const qualityLevelKeywords = Object.keys(QUALITY_LEVEL_KEYWORDS);
338
+ const qualityPattern = new RegExp(`@(${qualityLevelKeywords.join("|")})(?=\\s|$)`, "iu");
339
+ const qualityMatch = remaining.match(qualityPattern);
340
+ if (qualityMatch) {
341
+ const keyword = qualityMatch[1]!.toLowerCase();
342
+ workContext.qualityLevel = QUALITY_LEVEL_KEYWORDS[keyword];
343
+ remaining = remaining.replace(qualityMatch[0], " ").trim();
344
+ }
299
345
 
300
- // Extract contexts first (@focus, @review, @집중) - task-specific
346
+ // Extract contexts (@focus, @review, @집중) - task-specific
347
+ // Excluding qualityLevel keywords that were already processed
301
348
  const contextMatches = remaining.match(/@([\p{L}\p{N}_]+)/gu);
302
349
  if (contextMatches) {
303
- result.contexts = contextMatches.map((m) => m.slice(1));
350
+ const contexts = contextMatches
351
+ .map((m) => m.slice(1))
352
+ .filter((c) => !QUALITY_LEVEL_KEYWORDS[c.toLowerCase()]);
353
+ if (contexts.length > 0) {
354
+ result.contexts = contexts;
355
+ }
304
356
  for (const match of contextMatches) {
305
357
  remaining = remaining.replace(match, "").trim();
306
358
  }
@@ -362,6 +414,11 @@ export function parseTaskInput(input: string): TaskCreateInput {
362
414
  );
363
415
  }
364
416
 
417
+ // Add workContext if any fields were extracted
418
+ if (Object.keys(workContext).length > 0) {
419
+ result.workContext = workContext as WorkContext;
420
+ }
421
+
365
422
  return result;
366
423
  }
367
424