@task-mcp/shared 1.0.20 → 1.0.22
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/README.md +122 -0
- package/package.json +1 -6
- package/src/algorithms/critical-path.ts +31 -6
- package/src/algorithms/dependency-integrity.ts +5 -2
- package/src/algorithms/topological-sort.ts +66 -17
- package/src/utils/index.ts +5 -5
- package/src/utils/natural-language.ts +210 -83
- package/src/utils/projection.ts +8 -8
- package/src/utils/workspace.ts +16 -4
- package/dist/algorithms/critical-path.d.ts +0 -46
- package/dist/algorithms/critical-path.d.ts.map +0 -1
- package/dist/algorithms/critical-path.js +0 -320
- package/dist/algorithms/critical-path.js.map +0 -1
- package/dist/algorithms/critical-path.test.d.ts +0 -2
- package/dist/algorithms/critical-path.test.d.ts.map +0 -1
- package/dist/algorithms/critical-path.test.js +0 -194
- package/dist/algorithms/critical-path.test.js.map +0 -1
- package/dist/algorithms/dependency-integrity.d.ts +0 -81
- package/dist/algorithms/dependency-integrity.d.ts.map +0 -1
- package/dist/algorithms/dependency-integrity.js +0 -207
- package/dist/algorithms/dependency-integrity.js.map +0 -1
- package/dist/algorithms/dependency-integrity.test.d.ts +0 -2
- package/dist/algorithms/dependency-integrity.test.d.ts.map +0 -1
- package/dist/algorithms/dependency-integrity.test.js +0 -309
- package/dist/algorithms/dependency-integrity.test.js.map +0 -1
- package/dist/algorithms/index.d.ts +0 -5
- package/dist/algorithms/index.d.ts.map +0 -1
- package/dist/algorithms/index.js +0 -5
- package/dist/algorithms/index.js.map +0 -1
- package/dist/algorithms/tech-analysis.d.ts +0 -106
- package/dist/algorithms/tech-analysis.d.ts.map +0 -1
- package/dist/algorithms/tech-analysis.js +0 -344
- package/dist/algorithms/tech-analysis.js.map +0 -1
- package/dist/algorithms/tech-analysis.test.d.ts +0 -2
- package/dist/algorithms/tech-analysis.test.d.ts.map +0 -1
- package/dist/algorithms/tech-analysis.test.js +0 -338
- package/dist/algorithms/tech-analysis.test.js.map +0 -1
- package/dist/algorithms/topological-sort.d.ts +0 -41
- package/dist/algorithms/topological-sort.d.ts.map +0 -1
- package/dist/algorithms/topological-sort.js +0 -165
- package/dist/algorithms/topological-sort.js.map +0 -1
- package/dist/algorithms/topological-sort.test.d.ts +0 -2
- package/dist/algorithms/topological-sort.test.d.ts.map +0 -1
- package/dist/algorithms/topological-sort.test.js +0 -162
- package/dist/algorithms/topological-sort.test.js.map +0 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -7
- package/dist/index.js.map +0 -1
- package/dist/schemas/inbox.d.ts +0 -55
- package/dist/schemas/inbox.d.ts.map +0 -1
- package/dist/schemas/inbox.js +0 -25
- package/dist/schemas/inbox.js.map +0 -1
- package/dist/schemas/index.d.ts +0 -7
- package/dist/schemas/index.d.ts.map +0 -1
- package/dist/schemas/index.js +0 -17
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/project.d.ts +0 -177
- package/dist/schemas/project.d.ts.map +0 -1
- package/dist/schemas/project.js +0 -56
- package/dist/schemas/project.js.map +0 -1
- package/dist/schemas/response-format.d.ts +0 -148
- package/dist/schemas/response-format.d.ts.map +0 -1
- package/dist/schemas/response-format.js +0 -18
- package/dist/schemas/response-format.js.map +0 -1
- package/dist/schemas/response-schema.d.ts +0 -307
- package/dist/schemas/response-schema.d.ts.map +0 -1
- package/dist/schemas/response-schema.js +0 -75
- package/dist/schemas/response-schema.js.map +0 -1
- package/dist/schemas/response-schema.test.d.ts +0 -2
- package/dist/schemas/response-schema.test.d.ts.map +0 -1
- package/dist/schemas/response-schema.test.js +0 -256
- package/dist/schemas/response-schema.test.js.map +0 -1
- package/dist/schemas/state.d.ts +0 -17
- package/dist/schemas/state.d.ts.map +0 -1
- package/dist/schemas/state.js +0 -17
- package/dist/schemas/state.js.map +0 -1
- package/dist/schemas/task.d.ts +0 -881
- package/dist/schemas/task.d.ts.map +0 -1
- package/dist/schemas/task.js +0 -189
- package/dist/schemas/task.js.map +0 -1
- package/dist/schemas/view.d.ts +0 -143
- package/dist/schemas/view.d.ts.map +0 -1
- package/dist/schemas/view.js +0 -48
- package/dist/schemas/view.js.map +0 -1
- package/dist/utils/dashboard-renderer.d.ts +0 -93
- package/dist/utils/dashboard-renderer.d.ts.map +0 -1
- package/dist/utils/dashboard-renderer.js +0 -424
- package/dist/utils/dashboard-renderer.js.map +0 -1
- package/dist/utils/dashboard-renderer.test.d.ts +0 -2
- package/dist/utils/dashboard-renderer.test.d.ts.map +0 -1
- package/dist/utils/dashboard-renderer.test.js +0 -774
- package/dist/utils/dashboard-renderer.test.js.map +0 -1
- package/dist/utils/date.d.ts +0 -94
- package/dist/utils/date.d.ts.map +0 -1
- package/dist/utils/date.js +0 -323
- package/dist/utils/date.js.map +0 -1
- package/dist/utils/date.test.d.ts +0 -2
- package/dist/utils/date.test.d.ts.map +0 -1
- package/dist/utils/date.test.js +0 -276
- package/dist/utils/date.test.js.map +0 -1
- package/dist/utils/hierarchy.d.ts +0 -102
- package/dist/utils/hierarchy.d.ts.map +0 -1
- package/dist/utils/hierarchy.js +0 -236
- package/dist/utils/hierarchy.js.map +0 -1
- package/dist/utils/hierarchy.test.d.ts +0 -2
- package/dist/utils/hierarchy.test.d.ts.map +0 -1
- package/dist/utils/hierarchy.test.js +0 -436
- package/dist/utils/hierarchy.test.js.map +0 -1
- package/dist/utils/id.d.ts +0 -60
- package/dist/utils/id.d.ts.map +0 -1
- package/dist/utils/id.js +0 -118
- package/dist/utils/id.js.map +0 -1
- package/dist/utils/id.test.d.ts +0 -2
- package/dist/utils/id.test.d.ts.map +0 -1
- package/dist/utils/id.test.js +0 -193
- package/dist/utils/id.test.js.map +0 -1
- package/dist/utils/index.d.ts +0 -12
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -34
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/natural-language.d.ts +0 -57
- package/dist/utils/natural-language.d.ts.map +0 -1
- package/dist/utils/natural-language.js +0 -211
- package/dist/utils/natural-language.js.map +0 -1
- package/dist/utils/natural-language.test.d.ts +0 -2
- package/dist/utils/natural-language.test.d.ts.map +0 -1
- package/dist/utils/natural-language.test.js +0 -197
- package/dist/utils/natural-language.test.js.map +0 -1
- package/dist/utils/priority-queue.d.ts +0 -17
- package/dist/utils/priority-queue.d.ts.map +0 -1
- package/dist/utils/priority-queue.js +0 -62
- package/dist/utils/priority-queue.js.map +0 -1
- package/dist/utils/priority-queue.test.d.ts +0 -2
- package/dist/utils/priority-queue.test.d.ts.map +0 -1
- package/dist/utils/priority-queue.test.js +0 -82
- package/dist/utils/priority-queue.test.js.map +0 -1
- package/dist/utils/projection.d.ts +0 -65
- package/dist/utils/projection.d.ts.map +0 -1
- package/dist/utils/projection.js +0 -180
- package/dist/utils/projection.js.map +0 -1
- package/dist/utils/projection.test.d.ts +0 -2
- package/dist/utils/projection.test.d.ts.map +0 -1
- package/dist/utils/projection.test.js +0 -336
- package/dist/utils/projection.test.js.map +0 -1
- package/dist/utils/terminal-ui.d.ts +0 -208
- package/dist/utils/terminal-ui.d.ts.map +0 -1
- package/dist/utils/terminal-ui.js +0 -611
- package/dist/utils/terminal-ui.js.map +0 -1
- package/dist/utils/terminal-ui.test.d.ts +0 -2
- package/dist/utils/terminal-ui.test.d.ts.map +0 -1
- package/dist/utils/terminal-ui.test.js +0 -683
- package/dist/utils/terminal-ui.test.js.map +0 -1
- package/dist/utils/workspace.d.ts +0 -100
- package/dist/utils/workspace.d.ts.map +0 -1
- package/dist/utils/workspace.js +0 -173
- package/dist/utils/workspace.js.map +0 -1
- package/dist/utils/workspace.test.d.ts +0 -2
- package/dist/utils/workspace.test.d.ts.map +0 -1
- package/dist/utils/workspace.test.js +0 -97
- package/dist/utils/workspace.test.js.map +0 -1
- package/src/algorithms/critical-path.test.ts +0 -241
- package/src/algorithms/dependency-integrity.test.ts +0 -348
- package/src/algorithms/tech-analysis.test.ts +0 -413
- package/src/algorithms/topological-sort.test.ts +0 -190
- package/src/schemas/response-schema.test.ts +0 -314
- package/src/utils/dashboard-renderer.test.ts +0 -983
- package/src/utils/date.test.ts +0 -329
- package/src/utils/hierarchy.test.ts +0 -505
- package/src/utils/id.test.ts +0 -235
- package/src/utils/natural-language.test.ts +0 -242
- package/src/utils/priority-queue.test.ts +0 -103
- package/src/utils/projection.test.ts +0 -425
- package/src/utils/terminal-ui.test.ts +0 -831
- package/src/utils/workspace.test.ts +0 -125
|
@@ -30,6 +30,170 @@ function validateInputLength(input: string): void {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Extracted metadata from natural language input
|
|
35
|
+
*/
|
|
36
|
+
export interface ExtractedMetadata {
|
|
37
|
+
/** Extracted priority (!high, !medium, !low, !critical) */
|
|
38
|
+
priority?: Priority;
|
|
39
|
+
/** Extracted due date (today, tomorrow, next week, etc.) */
|
|
40
|
+
dueDate?: string;
|
|
41
|
+
/** Extracted tags (#tag) */
|
|
42
|
+
tags?: string[];
|
|
43
|
+
/** Extracted estimated time in minutes (~2h, ~30m) */
|
|
44
|
+
estimateMinutes?: number;
|
|
45
|
+
/** Remaining text after metadata extraction */
|
|
46
|
+
remaining: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Options for metadata extraction
|
|
51
|
+
*/
|
|
52
|
+
export interface ExtractMetadataOptions {
|
|
53
|
+
/** Extract priority markers (!high, !low, etc.) */
|
|
54
|
+
extractPriority?: boolean;
|
|
55
|
+
/** Extract date expressions (today, tomorrow, by Friday, etc.) */
|
|
56
|
+
extractDate?: boolean;
|
|
57
|
+
/** Extract hashtags (#tag) */
|
|
58
|
+
extractTags?: boolean;
|
|
59
|
+
/** Extract time estimates (~2h, ~30m) */
|
|
60
|
+
extractEstimate?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const DEFAULT_EXTRACT_OPTIONS: ExtractMetadataOptions = {
|
|
64
|
+
extractPriority: true,
|
|
65
|
+
extractDate: true,
|
|
66
|
+
extractTags: true,
|
|
67
|
+
extractEstimate: true,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Extract metadata from natural language input
|
|
72
|
+
*
|
|
73
|
+
* Supports:
|
|
74
|
+
* - Priority: !high, !medium, !low, !critical, !높음, !보통, !낮음, !긴급
|
|
75
|
+
* - Date: today, tomorrow, next week, 내일, 오늘, by Friday, 금요일까지
|
|
76
|
+
* - Tags: #tag, #개발, #backend
|
|
77
|
+
* - Time estimate: ~2h, ~30m, ~1h30m (with ~ prefix)
|
|
78
|
+
*
|
|
79
|
+
* @param input - Raw natural language input
|
|
80
|
+
* @param options - Options to control which metadata types to extract
|
|
81
|
+
* @returns Extracted metadata and remaining text
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const result = extractMetadata("Review PR tomorrow #dev !high ~2h");
|
|
86
|
+
* // {
|
|
87
|
+
* // priority: "high",
|
|
88
|
+
* // dueDate: "2025-01-01",
|
|
89
|
+
* // tags: ["dev"],
|
|
90
|
+
* // estimateMinutes: 120,
|
|
91
|
+
* // remaining: "Review PR"
|
|
92
|
+
* // }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export function extractMetadata(
|
|
96
|
+
input: string,
|
|
97
|
+
options: ExtractMetadataOptions = DEFAULT_EXTRACT_OPTIONS
|
|
98
|
+
): ExtractedMetadata {
|
|
99
|
+
validateInputLength(input);
|
|
100
|
+
let remaining = input.trim();
|
|
101
|
+
const result: ExtractedMetadata = { remaining: "" };
|
|
102
|
+
|
|
103
|
+
// Extract priority (!high, !critical, !medium, !low, !높음, !보통, !낮음)
|
|
104
|
+
if (options.extractPriority !== false) {
|
|
105
|
+
const priorityMatch = remaining.match(/!([\p{L}\p{N}_]+)/gu);
|
|
106
|
+
if (priorityMatch) {
|
|
107
|
+
for (const match of priorityMatch) {
|
|
108
|
+
const priority = parsePriority(match.slice(1));
|
|
109
|
+
if (priority) {
|
|
110
|
+
result.priority = priority;
|
|
111
|
+
remaining = remaining.replace(match, "").trim();
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Extract tags (#dev, #backend, #개발)
|
|
119
|
+
if (options.extractTags !== false) {
|
|
120
|
+
const tagMatches = remaining.match(/#([\p{L}\p{N}_]+)/gu);
|
|
121
|
+
if (tagMatches) {
|
|
122
|
+
result.tags = tagMatches.map((m) => m.slice(1));
|
|
123
|
+
for (const match of tagMatches) {
|
|
124
|
+
remaining = remaining.replace(match, "").trim();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Extract due date patterns
|
|
130
|
+
if (options.extractDate !== false) {
|
|
131
|
+
// "by Friday", "by tomorrow", "until next week"
|
|
132
|
+
const byMatch = remaining.match(/\b(by|until|before)\s+(\w+(\s+\w+)?)/i);
|
|
133
|
+
if (byMatch) {
|
|
134
|
+
const dateStr = byMatch[2]!;
|
|
135
|
+
const date = parseRelativeDate(dateStr);
|
|
136
|
+
if (date) {
|
|
137
|
+
result.dueDate = formatDate(date);
|
|
138
|
+
remaining = remaining.replace(byMatch[0], "").trim();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Korean date patterns: "내일까지", "금요일까지"
|
|
143
|
+
if (!result.dueDate) {
|
|
144
|
+
const koreanDueMatch = remaining.match(/(\S+)까지/);
|
|
145
|
+
if (koreanDueMatch) {
|
|
146
|
+
const dateStr = koreanDueMatch[1]!;
|
|
147
|
+
const date = parseRelativeDate(dateStr);
|
|
148
|
+
if (date) {
|
|
149
|
+
result.dueDate = formatDate(date);
|
|
150
|
+
remaining = remaining.replace(koreanDueMatch[0], "").trim();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// "tomorrow", "today" at the end
|
|
156
|
+
if (!result.dueDate) {
|
|
157
|
+
const dateWords = ["tomorrow", "today", "내일", "오늘", "모레"];
|
|
158
|
+
for (const word of dateWords) {
|
|
159
|
+
if (remaining.toLowerCase().endsWith(word)) {
|
|
160
|
+
const date = parseRelativeDate(word);
|
|
161
|
+
if (date) {
|
|
162
|
+
result.dueDate = formatDate(date);
|
|
163
|
+
remaining = remaining.slice(0, -word.length).trim();
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Extract time estimate (~2h, ~30m, ~1h30m)
|
|
172
|
+
if (options.extractEstimate !== false) {
|
|
173
|
+
const timeMatch = remaining.match(/~(\d+h\d+m|\d+h|\d+m)\b/);
|
|
174
|
+
if (timeMatch) {
|
|
175
|
+
let minutes = 0;
|
|
176
|
+
const hoursMatch = timeMatch[1]!.match(/(\d+)h/);
|
|
177
|
+
const minsMatch = timeMatch[1]!.match(/(\d+)m/);
|
|
178
|
+
if (hoursMatch) {
|
|
179
|
+
minutes += parseInt(hoursMatch[1]!, 10) * 60;
|
|
180
|
+
}
|
|
181
|
+
if (minsMatch) {
|
|
182
|
+
minutes += parseInt(minsMatch[1]!, 10);
|
|
183
|
+
}
|
|
184
|
+
if (minutes > 0) {
|
|
185
|
+
result.estimateMinutes = minutes;
|
|
186
|
+
remaining = remaining.replace(timeMatch[0], "").trim();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Clean up extra spaces
|
|
192
|
+
result.remaining = remaining.replace(/\s+/g, " ").trim();
|
|
193
|
+
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
33
197
|
/**
|
|
34
198
|
* Target type for parsed input
|
|
35
199
|
*/
|
|
@@ -94,21 +258,21 @@ export function parseInput(input: string): ParsedInput {
|
|
|
94
258
|
* - "성능 개선 아이디어 #performance"
|
|
95
259
|
*/
|
|
96
260
|
export function parseInboxInput(input: string): InboxCreateInput {
|
|
97
|
-
|
|
98
|
-
|
|
261
|
+
// Inbox only extracts tags
|
|
262
|
+
const metadata = extractMetadata(input, {
|
|
263
|
+
extractPriority: false,
|
|
264
|
+
extractDate: false,
|
|
265
|
+
extractTags: true,
|
|
266
|
+
extractEstimate: false,
|
|
267
|
+
});
|
|
268
|
+
|
|
99
269
|
const result: InboxCreateInput = { content: "" };
|
|
100
270
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (tagMatches) {
|
|
104
|
-
result.tags = tagMatches.map((m) => m.slice(1));
|
|
105
|
-
for (const match of tagMatches) {
|
|
106
|
-
remaining = remaining.replace(match, "").trim();
|
|
107
|
-
}
|
|
271
|
+
if (metadata.tags) {
|
|
272
|
+
result.tags = metadata.tags;
|
|
108
273
|
}
|
|
109
274
|
|
|
110
|
-
|
|
111
|
-
result.content = remaining.replace(/\s+/g, " ").trim();
|
|
275
|
+
result.content = metadata.remaining;
|
|
112
276
|
|
|
113
277
|
if (!result.content) {
|
|
114
278
|
throw new InputValidationError(
|
|
@@ -123,7 +287,7 @@ export function parseInboxInput(input: string): InboxCreateInput {
|
|
|
123
287
|
* Parse natural language task input
|
|
124
288
|
*
|
|
125
289
|
* Examples:
|
|
126
|
-
* - "Review PR tomorrow #dev !high"
|
|
290
|
+
* - "Review PR tomorrow #dev !high ~2h"
|
|
127
291
|
* - "내일까지 보고서 작성 #업무 !높음 @집중"
|
|
128
292
|
* - "Fix bug by Friday #backend !critical"
|
|
129
293
|
* - "Write tests every Monday #testing"
|
|
@@ -133,20 +297,7 @@ export function parseTaskInput(input: string): TaskCreateInput {
|
|
|
133
297
|
let remaining = input.trim();
|
|
134
298
|
const result: TaskCreateInput = { title: "" };
|
|
135
299
|
|
|
136
|
-
// Extract
|
|
137
|
-
const priorityMatch = remaining.match(/!([\p{L}\p{N}_]+)/gu);
|
|
138
|
-
if (priorityMatch) {
|
|
139
|
-
for (const match of priorityMatch) {
|
|
140
|
-
const priority = parsePriority(match.slice(1));
|
|
141
|
-
if (priority) {
|
|
142
|
-
result.priority = priority;
|
|
143
|
-
remaining = remaining.replace(match, "").trim();
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Extract contexts (@focus, @review, @집중)
|
|
300
|
+
// Extract contexts first (@focus, @review, @집중) - task-specific
|
|
150
301
|
const contextMatches = remaining.match(/@([\p{L}\p{N}_]+)/gu);
|
|
151
302
|
if (contextMatches) {
|
|
152
303
|
result.contexts = contextMatches.map((m) => m.slice(1));
|
|
@@ -155,57 +306,20 @@ export function parseTaskInput(input: string): TaskCreateInput {
|
|
|
155
306
|
}
|
|
156
307
|
}
|
|
157
308
|
|
|
158
|
-
// Extract
|
|
159
|
-
const
|
|
160
|
-
if (
|
|
161
|
-
result.
|
|
162
|
-
|
|
163
|
-
remaining = remaining.replace(match, "").trim();
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Extract due date patterns
|
|
168
|
-
// "by Friday", "by tomorrow", "until next week"
|
|
169
|
-
const byMatch = remaining.match(/\b(by|until|before)\s+(\w+(\s+\w+)?)/i);
|
|
170
|
-
if (byMatch) {
|
|
171
|
-
const dateStr = byMatch[2]!;
|
|
172
|
-
const date = parseRelativeDate(dateStr);
|
|
173
|
-
if (date) {
|
|
174
|
-
result.dueDate = formatDate(date);
|
|
175
|
-
remaining = remaining.replace(byMatch[0], "").trim();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Korean date patterns: "내일까지", "금요일까지"
|
|
180
|
-
const koreanDueMatch = remaining.match(/(\S+)까지/);
|
|
181
|
-
if (koreanDueMatch && !result.dueDate) {
|
|
182
|
-
const dateStr = koreanDueMatch[1]!;
|
|
183
|
-
const date = parseRelativeDate(dateStr);
|
|
184
|
-
if (date) {
|
|
185
|
-
result.dueDate = formatDate(date);
|
|
186
|
-
remaining = remaining.replace(koreanDueMatch[0], "").trim();
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// "tomorrow", "today" at the end
|
|
191
|
-
const dateWords = ["tomorrow", "today", "내일", "오늘", "모레"];
|
|
192
|
-
for (const word of dateWords) {
|
|
193
|
-
if (remaining.toLowerCase().endsWith(word) && !result.dueDate) {
|
|
194
|
-
const date = parseRelativeDate(word);
|
|
195
|
-
if (date) {
|
|
196
|
-
result.dueDate = formatDate(date);
|
|
197
|
-
remaining = remaining.slice(0, -word.length).trim();
|
|
198
|
-
}
|
|
199
|
-
}
|
|
309
|
+
// Extract sortOrder (^1, ^10) - task-specific
|
|
310
|
+
const sortMatch = remaining.match(/\^(\d+)/);
|
|
311
|
+
if (sortMatch) {
|
|
312
|
+
result.sortOrder = parseInt(sortMatch[1]!, 10);
|
|
313
|
+
remaining = remaining.replace(sortMatch[0], "").trim();
|
|
200
314
|
}
|
|
201
315
|
|
|
202
|
-
// Extract time estimate (30m, 2h, 1h30m)
|
|
203
|
-
//
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
316
|
+
// Extract time estimate without ~ prefix for backward compatibility (30m, 2h, 1h30m)
|
|
317
|
+
// This handles the legacy format before extractMetadata handles ~prefix format
|
|
318
|
+
const legacyTimeMatch = remaining.match(/\b(\d+h\d+m|\d+h|\d+m)\b/);
|
|
319
|
+
if (legacyTimeMatch && !remaining.includes("~" + legacyTimeMatch[1])) {
|
|
206
320
|
let minutes = 0;
|
|
207
|
-
const hoursMatch =
|
|
208
|
-
const minsMatch =
|
|
321
|
+
const hoursMatch = legacyTimeMatch[0].match(/(\d+)h/);
|
|
322
|
+
const minsMatch = legacyTimeMatch[0].match(/(\d+)m/);
|
|
209
323
|
if (hoursMatch) {
|
|
210
324
|
minutes += parseInt(hoursMatch[1]!, 10) * 60;
|
|
211
325
|
}
|
|
@@ -214,20 +328,33 @@ export function parseTaskInput(input: string): TaskCreateInput {
|
|
|
214
328
|
}
|
|
215
329
|
if (minutes > 0) {
|
|
216
330
|
result.estimate = { expected: minutes, confidence: "medium" };
|
|
217
|
-
remaining = remaining.replace(
|
|
331
|
+
remaining = remaining.replace(legacyTimeMatch[0], "").trim();
|
|
218
332
|
}
|
|
219
333
|
}
|
|
220
334
|
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
335
|
+
// Use extractMetadata for common patterns
|
|
336
|
+
const metadata = extractMetadata(remaining, {
|
|
337
|
+
extractPriority: true,
|
|
338
|
+
extractDate: true,
|
|
339
|
+
extractTags: true,
|
|
340
|
+
extractEstimate: true,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Apply extracted metadata
|
|
344
|
+
if (metadata.priority) {
|
|
345
|
+
result.priority = metadata.priority;
|
|
346
|
+
}
|
|
347
|
+
if (metadata.dueDate) {
|
|
348
|
+
result.dueDate = metadata.dueDate;
|
|
349
|
+
}
|
|
350
|
+
if (metadata.tags) {
|
|
351
|
+
result.tags = metadata.tags;
|
|
352
|
+
}
|
|
353
|
+
if (metadata.estimateMinutes && !result.estimate) {
|
|
354
|
+
result.estimate = { expected: metadata.estimateMinutes, confidence: "medium" };
|
|
227
355
|
}
|
|
228
356
|
|
|
229
|
-
|
|
230
|
-
result.title = remaining.replace(/\s+/g, " ").trim();
|
|
357
|
+
result.title = metadata.remaining;
|
|
231
358
|
|
|
232
359
|
if (!result.title) {
|
|
233
360
|
throw new InputValidationError(
|
package/src/utils/projection.ts
CHANGED
|
@@ -27,7 +27,7 @@ function assertNever(value: never): never {
|
|
|
27
27
|
/**
|
|
28
28
|
* Project a single task to the specified format
|
|
29
29
|
*/
|
|
30
|
-
export function
|
|
30
|
+
export function formatTask(task: Task, format: ResponseFormat): TaskSummary | TaskPreview | Task {
|
|
31
31
|
switch (format) {
|
|
32
32
|
case "concise":
|
|
33
33
|
return {
|
|
@@ -59,19 +59,19 @@ export function projectTask(task: Task, format: ResponseFormat): TaskSummary | T
|
|
|
59
59
|
/**
|
|
60
60
|
* Project multiple tasks with optional limit
|
|
61
61
|
*/
|
|
62
|
-
export function
|
|
62
|
+
export function formatTasks(
|
|
63
63
|
tasks: Task[],
|
|
64
64
|
format: ResponseFormat,
|
|
65
65
|
limit?: number
|
|
66
66
|
): (TaskSummary | TaskPreview | Task)[] {
|
|
67
67
|
const sliced = limit ? tasks.slice(0, limit) : tasks;
|
|
68
|
-
return sliced.map((task) =>
|
|
68
|
+
return sliced.map((task) => formatTask(task, format));
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* Project tasks with pagination
|
|
73
73
|
*/
|
|
74
|
-
export function
|
|
74
|
+
export function formatTasksPaginated(
|
|
75
75
|
tasks: Task[],
|
|
76
76
|
format: ResponseFormat,
|
|
77
77
|
limit: number = 20,
|
|
@@ -79,7 +79,7 @@ export function projectTasksPaginated(
|
|
|
79
79
|
): PaginatedResponse<TaskSummary | TaskPreview | Task> {
|
|
80
80
|
const effectiveLimit = Math.min(limit, 100);
|
|
81
81
|
const sliced = tasks.slice(offset, offset + effectiveLimit);
|
|
82
|
-
const projected = sliced.map((task) =>
|
|
82
|
+
const projected = sliced.map((task) => formatTask(task, format));
|
|
83
83
|
|
|
84
84
|
return {
|
|
85
85
|
items: projected,
|
|
@@ -93,7 +93,7 @@ export function projectTasksPaginated(
|
|
|
93
93
|
/**
|
|
94
94
|
* Project a single inbox item to the specified format
|
|
95
95
|
*/
|
|
96
|
-
export function
|
|
96
|
+
export function formatInboxItem(
|
|
97
97
|
item: InboxItem,
|
|
98
98
|
format: ResponseFormat
|
|
99
99
|
): InboxSummary | InboxPreview | InboxItem {
|
|
@@ -124,13 +124,13 @@ export function projectInboxItem(
|
|
|
124
124
|
/**
|
|
125
125
|
* Project multiple inbox items with optional limit
|
|
126
126
|
*/
|
|
127
|
-
export function
|
|
127
|
+
export function formatInboxItems(
|
|
128
128
|
items: InboxItem[],
|
|
129
129
|
format: ResponseFormat,
|
|
130
130
|
limit?: number
|
|
131
131
|
): (InboxSummary | InboxPreview | InboxItem)[] {
|
|
132
132
|
const sliced = limit ? items.slice(0, limit) : items;
|
|
133
|
-
return sliced.map((item) =>
|
|
133
|
+
return sliced.map((item) => formatInboxItem(item, format));
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
/**
|
package/src/utils/workspace.ts
CHANGED
|
@@ -7,12 +7,18 @@
|
|
|
7
7
|
* 3. Current working directory basename (final fallback)
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { exec } from "node:child_process";
|
|
10
|
+
import { exec, execSync } from "node:child_process";
|
|
11
11
|
import { promisify } from "node:util";
|
|
12
12
|
import { basename } from "node:path";
|
|
13
13
|
|
|
14
14
|
const execAsync = promisify(exec);
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Maximum length for workspace names.
|
|
18
|
+
* Prevents excessive path lengths and potential DoS via long names.
|
|
19
|
+
*/
|
|
20
|
+
const MAX_WORKSPACE_LENGTH = 64;
|
|
21
|
+
|
|
16
22
|
/**
|
|
17
23
|
* Normalize a string to a valid workspace name.
|
|
18
24
|
*
|
|
@@ -21,6 +27,7 @@ const execAsync = promisify(exec);
|
|
|
21
27
|
* - Replace spaces with hyphens
|
|
22
28
|
* - Keep alphanumeric, hyphens, and underscores
|
|
23
29
|
* - Trim leading/trailing hyphens
|
|
30
|
+
* - Limit to MAX_WORKSPACE_LENGTH characters
|
|
24
31
|
*
|
|
25
32
|
* @param name - Raw name to normalize
|
|
26
33
|
* @returns Normalized workspace name
|
|
@@ -29,15 +36,19 @@ const execAsync = promisify(exec);
|
|
|
29
36
|
* ```typescript
|
|
30
37
|
* normalizeWorkspace('My Project'); // 'my-project'
|
|
31
38
|
* normalizeWorkspace('Task-MCP'); // 'task-mcp'
|
|
39
|
+
* normalizeWorkspace('a'.repeat(100)); // 64 chars max
|
|
32
40
|
* ```
|
|
33
41
|
*/
|
|
34
42
|
export function normalizeWorkspace(name: string): string {
|
|
35
|
-
|
|
43
|
+
const normalized = name
|
|
36
44
|
.toLowerCase()
|
|
37
45
|
.replace(/\s+/g, "-")
|
|
38
46
|
.replace(/[^a-z0-9_-]/g, "")
|
|
39
47
|
.replace(/^-+|-+$/g, "")
|
|
40
|
-
|
|
48
|
+
.slice(0, MAX_WORKSPACE_LENGTH) // Enforce length limit
|
|
49
|
+
.replace(/-+$/, ""); // Clean trailing hyphens after truncation
|
|
50
|
+
|
|
51
|
+
return normalized || "default";
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
/**
|
|
@@ -66,11 +77,12 @@ export async function getGitRepoRoot(cwd?: string): Promise<string | null> {
|
|
|
66
77
|
*/
|
|
67
78
|
export function getGitRepoRootSync(cwd?: string): string | null {
|
|
68
79
|
try {
|
|
69
|
-
const { execSync } = require("node:child_process");
|
|
70
80
|
const result = execSync("git rev-parse --show-toplevel", {
|
|
71
81
|
cwd: cwd ?? process.cwd(),
|
|
72
82
|
encoding: "utf-8",
|
|
73
83
|
stdio: ["pipe", "pipe", "pipe"],
|
|
84
|
+
timeout: 5000, // 5 second timeout
|
|
85
|
+
maxBuffer: 1024, // Limit output buffer
|
|
74
86
|
});
|
|
75
87
|
return (result as string).trim() || null;
|
|
76
88
|
} catch {
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { Task } from "../schemas/task.js";
|
|
2
|
-
/**
|
|
3
|
-
* Task with computed CPM (Critical Path Method) values
|
|
4
|
-
*/
|
|
5
|
-
export interface CPMTask extends Task {
|
|
6
|
-
earliestStart: number;
|
|
7
|
-
earliestFinish: number;
|
|
8
|
-
latestStart: number;
|
|
9
|
-
latestFinish: number;
|
|
10
|
-
slack: number;
|
|
11
|
-
isCritical: boolean;
|
|
12
|
-
dependentCount: number;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Result of critical path analysis
|
|
16
|
-
*/
|
|
17
|
-
export interface CPMResult {
|
|
18
|
-
tasks: CPMTask[];
|
|
19
|
-
criticalPath: CPMTask[];
|
|
20
|
-
projectDuration: number;
|
|
21
|
-
bottlenecks: CPMTask[];
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Perform Critical Path Method analysis
|
|
25
|
-
*
|
|
26
|
-
* CPM calculates:
|
|
27
|
-
* - Earliest Start (ES): Earliest a task can start
|
|
28
|
-
* - Earliest Finish (EF): ES + duration
|
|
29
|
-
* - Latest Finish (LF): Latest a task can finish without delaying project
|
|
30
|
-
* - Latest Start (LS): LF - duration
|
|
31
|
-
* - Slack: LS - ES (or LF - EF)
|
|
32
|
-
* - Critical Path: Tasks with slack = 0
|
|
33
|
-
*/
|
|
34
|
-
export declare function criticalPathAnalysis(tasks: Task[]): CPMResult;
|
|
35
|
-
/**
|
|
36
|
-
* Find tasks that can be executed in parallel (no dependencies between them)
|
|
37
|
-
*/
|
|
38
|
-
export declare function findParallelTasks(tasks: Task[]): Task[][];
|
|
39
|
-
/**
|
|
40
|
-
* Suggest the next best task to work on
|
|
41
|
-
*/
|
|
42
|
-
export declare function suggestNextTask(tasks: Task[], options?: {
|
|
43
|
-
contexts?: string[];
|
|
44
|
-
maxMinutes?: number;
|
|
45
|
-
}): Task | null;
|
|
46
|
-
//# sourceMappingURL=critical-path.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"critical-path.d.ts","sourceRoot":"","sources":["../../src/algorithms/critical-path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAO/C;;GAEG;AACH,MAAM,WAAW,OAAQ,SAAQ,IAAI;IAEnC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,YAAY,EAAE,OAAO,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,OAAO,EAAE,CAAC;CACxB;AA8ND;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,SAAS,CAmD7D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,EAAE,CAiDzD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,GAAE;IACP,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CAChB,GACL,IAAI,GAAG,IAAI,CAyDb"}
|