@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.
- package/dist/schemas/index.d.ts +2 -2
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +2 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/response-format.d.ts +8 -0
- package/dist/schemas/response-format.d.ts.map +1 -1
- package/dist/schemas/response-format.js.map +1 -1
- package/dist/schemas/task.d.ts +285 -0
- package/dist/schemas/task.d.ts.map +1 -1
- package/dist/schemas/task.js +44 -0
- package/dist/schemas/task.js.map +1 -1
- package/dist/schemas/work-context.test.d.ts +2 -0
- package/dist/schemas/work-context.test.d.ts.map +1 -0
- package/dist/schemas/work-context.test.js +192 -0
- package/dist/schemas/work-context.test.js.map +1 -0
- package/dist/utils/natural-language.d.ts.map +1 -1
- package/dist/utils/natural-language.js +54 -2
- package/dist/utils/natural-language.js.map +1 -1
- package/dist/utils/natural-language.test.js +46 -0
- package/dist/utils/natural-language.test.js.map +1 -1
- package/dist/utils/projection.d.ts.map +1 -1
- package/dist/utils/projection.js +29 -8
- package/dist/utils/projection.js.map +1 -1
- package/package.json +3 -2
- package/src/schemas/index.ts +5 -0
- package/src/schemas/response-format.ts +13 -1
- package/src/schemas/task.ts +55 -0
- package/src/schemas/work-context.test.ts +221 -0
- package/src/utils/natural-language.test.ts +56 -0
- package/src/utils/natural-language.ts +60 -3
- package/src/utils/projection.ts +29 -8
package/src/schemas/task.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
package/src/utils/projection.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|