@task-mcp/shared 1.0.26 → 1.0.28

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.
@@ -125,6 +125,54 @@ export const TechStackAnalysis = z.object({
125
125
  });
126
126
  export type TechStackAnalysis = z.infer<typeof TechStackAnalysis>;
127
127
 
128
+ // Quality level for work context
129
+ export const QualityLevel = z.enum(["quick", "standard", "thorough", "exhaustive"]);
130
+ export type QualityLevel = z.infer<typeof QualityLevel>;
131
+
132
+ // Work patterns for task execution
133
+ export const WorkPatterns = z.object({
134
+ parallelExecution: z.boolean().optional(), // Use parallel agents
135
+ fileByFileAnalysis: z.boolean().optional(), // Analyze each file individually
136
+ testFirst: z.boolean().optional(), // Write tests before implementation
137
+ reviewRequired: z.boolean().optional(), // Require review before completion
138
+ });
139
+ export type WorkPatterns = z.infer<typeof WorkPatterns>;
140
+
141
+ /**
142
+ * WorkContext - Captures the "how" of task execution
143
+ *
144
+ * When a session is interrupted and resumed, this context enables
145
+ * Claude to continue with the same quality level and methodology.
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * workContext: {
150
+ * intent: "Migrate API layer to service pattern",
151
+ * methodology: "Analyze each file → Review types → Extract service → Test",
152
+ * qualityLevel: "thorough",
153
+ * patterns: { fileByFileAnalysis: true, reviewRequired: true },
154
+ * acceptanceCriteria: ["All tests pass", "No type errors"]
155
+ * }
156
+ * ```
157
+ */
158
+ export const WorkContext = z.object({
159
+ // Work intent - why this task exists
160
+ intent: z.string().optional(),
161
+
162
+ // Methodology - how to approach the work
163
+ methodology: z.string().optional(),
164
+
165
+ // Quality level - how thorough the work should be
166
+ qualityLevel: QualityLevel.optional(),
167
+
168
+ // Work patterns - specific execution patterns
169
+ patterns: WorkPatterns.optional(),
170
+
171
+ // Acceptance criteria - conditions for completion
172
+ acceptanceCriteria: z.array(z.string()).optional(),
173
+ });
174
+ export type WorkContext = z.infer<typeof WorkContext>;
175
+
128
176
  // Core Task schema
129
177
  export const Task = z.object({
130
178
  id: z.string(),
@@ -170,6 +218,9 @@ export const Task = z.object({
170
218
  // Analysis fields (populated by Claude)
171
219
  complexity: ComplexityAnalysis.optional(),
172
220
  techStack: TechStackAnalysis.optional(),
221
+
222
+ // Work context - captures execution methodology for session resumption
223
+ workContext: WorkContext.optional(),
173
224
  });
174
225
  export type Task = z.infer<typeof Task>;
175
226
 
@@ -188,6 +239,8 @@ export const TaskCreateInput = z.object({
188
239
  tags: z.array(z.string()).optional(),
189
240
  sortOrder: z.number().optional(),
190
241
  recurrence: Recurrence.optional(),
242
+ // Work context for session resumption
243
+ workContext: WorkContext.optional(),
191
244
  });
192
245
  export type TaskCreateInput = z.infer<typeof TaskCreateInput>;
193
246
 
@@ -211,5 +264,7 @@ export const TaskUpdateInput = z.object({
211
264
  // Analysis fields
212
265
  complexity: ComplexityAnalysis.optional(),
213
266
  techStack: TechStackAnalysis.optional(),
267
+ // Work context for session resumption
268
+ workContext: WorkContext.optional(),
214
269
  });
215
270
  export type TaskUpdateInput = z.infer<typeof TaskUpdateInput>;
@@ -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
+ });
@@ -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
 
@@ -29,25 +29,46 @@ function assertNever(value: never): never {
29
29
  */
30
30
  export function formatTask(task: Task, format: ResponseFormat): TaskSummary | TaskPreview | Task {
31
31
  switch (format) {
32
- case "concise":
33
- return {
32
+ case "concise": {
33
+ const summary: TaskSummary = {
34
34
  id: task.id,
35
35
  title: task.title,
36
36
  status: task.status,
37
37
  priority: task.priority,
38
38
  };
39
+ // Include intent + qualityLevel for quick context understanding
40
+ if (task.workContext?.intent) {
41
+ summary.intent = task.workContext.intent;
42
+ }
43
+ if (task.workContext?.qualityLevel) {
44
+ summary.quality = task.workContext.qualityLevel;
45
+ }
46
+ return summary;
47
+ }
39
48
 
40
- case "standard":
41
- return {
49
+ case "standard": {
50
+ // Build result with only defined optional fields
51
+ const result: TaskPreview = {
42
52
  id: task.id,
43
53
  title: task.title,
44
54
  status: task.status,
45
55
  priority: task.priority,
46
- dueDate: task.dueDate,
47
- tags: task.tags,
48
- contexts: task.contexts,
49
- parentId: task.parentId,
56
+ ...(task.dueDate !== undefined && { dueDate: task.dueDate }),
57
+ ...(task.tags !== undefined && { tags: task.tags }),
58
+ ...(task.contexts !== undefined && { contexts: task.contexts }),
59
+ ...(task.parentId != null && { parentId: task.parentId }),
50
60
  };
61
+ // Include workContext summary if present (only defined fields)
62
+ if (task.workContext) {
63
+ const wc = task.workContext;
64
+ result.workContext = {
65
+ ...(wc.intent !== undefined && { intent: wc.intent }),
66
+ ...(wc.methodology !== undefined && { methodology: wc.methodology }),
67
+ ...(wc.qualityLevel !== undefined && { qualityLevel: wc.qualityLevel }),
68
+ };
69
+ }
70
+ return result;
71
+ }
51
72
 
52
73
  case "detailed":
53
74
  return task;