@task-mcp/shared 1.0.3 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/algorithms/critical-path.d.ts.map +1 -1
- package/dist/algorithms/critical-path.js +50 -26
- package/dist/algorithms/critical-path.js.map +1 -1
- package/dist/algorithms/dependency-integrity.d.ts +73 -0
- package/dist/algorithms/dependency-integrity.d.ts.map +1 -0
- package/dist/algorithms/dependency-integrity.js +189 -0
- package/dist/algorithms/dependency-integrity.js.map +1 -0
- package/dist/algorithms/index.d.ts +2 -0
- package/dist/algorithms/index.d.ts.map +1 -1
- package/dist/algorithms/index.js +2 -0
- package/dist/algorithms/index.js.map +1 -1
- package/dist/algorithms/tech-analysis.d.ts +106 -0
- package/dist/algorithms/tech-analysis.d.ts.map +1 -0
- package/dist/algorithms/tech-analysis.js +296 -0
- package/dist/algorithms/tech-analysis.js.map +1 -0
- package/dist/algorithms/tech-analysis.test.d.ts +2 -0
- package/dist/algorithms/tech-analysis.test.d.ts.map +1 -0
- package/dist/algorithms/tech-analysis.test.js +338 -0
- package/dist/algorithms/tech-analysis.test.js.map +1 -0
- package/dist/algorithms/topological-sort.d.ts.map +1 -1
- package/dist/algorithms/topological-sort.js +60 -8
- package/dist/algorithms/topological-sort.js.map +1 -1
- package/dist/schemas/inbox.d.ts +24 -0
- package/dist/schemas/inbox.d.ts.map +1 -0
- package/dist/schemas/inbox.js +25 -0
- package/dist/schemas/inbox.js.map +1 -0
- package/dist/schemas/index.d.ts +3 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +9 -1
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/response-format.d.ts +79 -0
- package/dist/schemas/response-format.d.ts.map +1 -0
- package/dist/schemas/response-format.js +17 -0
- package/dist/schemas/response-format.js.map +1 -0
- package/dist/schemas/task.d.ts +57 -0
- package/dist/schemas/task.d.ts.map +1 -1
- package/dist/schemas/task.js +34 -0
- package/dist/schemas/task.js.map +1 -1
- package/dist/utils/date.d.ts.map +1 -1
- package/dist/utils/date.js +17 -2
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/hierarchy.d.ts +75 -0
- package/dist/utils/hierarchy.d.ts.map +1 -0
- package/dist/utils/hierarchy.js +179 -0
- package/dist/utils/hierarchy.js.map +1 -0
- package/dist/utils/id.d.ts +51 -1
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +124 -4
- package/dist/utils/id.js.map +1 -1
- package/dist/utils/id.test.d.ts +2 -0
- package/dist/utils/id.test.d.ts.map +1 -0
- package/dist/utils/id.test.js +228 -0
- package/dist/utils/id.test.js.map +1 -0
- package/dist/utils/index.d.ts +4 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +7 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/natural-language.d.ts +45 -0
- package/dist/utils/natural-language.d.ts.map +1 -1
- package/dist/utils/natural-language.js +86 -0
- package/dist/utils/natural-language.js.map +1 -1
- package/dist/utils/projection.d.ts +65 -0
- package/dist/utils/projection.d.ts.map +1 -0
- package/dist/utils/projection.js +181 -0
- package/dist/utils/projection.js.map +1 -0
- package/dist/utils/projection.test.d.ts +2 -0
- package/dist/utils/projection.test.d.ts.map +1 -0
- package/dist/utils/projection.test.js +400 -0
- package/dist/utils/projection.test.js.map +1 -0
- package/package.json +1 -1
- package/src/algorithms/critical-path.ts +56 -24
- package/src/algorithms/dependency-integrity.ts +270 -0
- package/src/algorithms/index.ts +28 -0
- package/src/algorithms/tech-analysis.test.ts +413 -0
- package/src/algorithms/tech-analysis.ts +412 -0
- package/src/algorithms/topological-sort.ts +66 -9
- package/src/schemas/inbox.ts +32 -0
- package/src/schemas/index.ts +31 -0
- package/src/schemas/response-format.ts +108 -0
- package/src/schemas/task.ts +50 -0
- package/src/utils/date.ts +18 -2
- package/src/utils/hierarchy.ts +224 -0
- package/src/utils/id.test.ts +281 -0
- package/src/utils/id.ts +139 -4
- package/src/utils/index.ts +46 -2
- package/src/utils/natural-language.ts +113 -0
- package/src/utils/projection.test.ts +505 -0
- package/src/utils/projection.ts +251 -0
package/src/utils/id.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generate a unique ID with optional prefix
|
|
2
|
+
* Generate a unique ID with optional prefix.
|
|
3
|
+
* Uses crypto.randomUUID for secure, collision-resistant ID generation.
|
|
3
4
|
*/
|
|
4
5
|
export function generateId(prefix: string = ""): string {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
// Use crypto.randomUUID for better randomness and collision resistance
|
|
7
|
+
// Format: prefix_xxxxxxxx (8 chars from UUID, sufficient for local uniqueness)
|
|
8
|
+
const uuid = crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
9
|
+
return prefix ? `${prefix}_${uuid}` : uuid;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
/**
|
|
@@ -43,3 +45,136 @@ export function isValidProjectId(id: string): boolean {
|
|
|
43
45
|
export function isValidTaskId(id: string): boolean {
|
|
44
46
|
return /^task_[a-z0-9]+$/.test(id);
|
|
45
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate an inbox item ID
|
|
51
|
+
*/
|
|
52
|
+
export function generateInboxId(): string {
|
|
53
|
+
return generateId("inbox");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Validate an inbox ID format
|
|
58
|
+
* @returns true if valid format (inbox_[alphanumeric])
|
|
59
|
+
*/
|
|
60
|
+
export function isValidInboxId(id: string): boolean {
|
|
61
|
+
return /^inbox_[a-z0-9]+$/.test(id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* ID validation error with detailed context
|
|
66
|
+
*/
|
|
67
|
+
export class InvalidIdError extends Error {
|
|
68
|
+
constructor(
|
|
69
|
+
public readonly idType: "task" | "project" | "inbox" | "view",
|
|
70
|
+
public readonly invalidValue: unknown,
|
|
71
|
+
public readonly reason: string
|
|
72
|
+
) {
|
|
73
|
+
super(`Invalid ${idType} ID: ${reason}`);
|
|
74
|
+
this.name = "InvalidIdError";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validation result with reason if invalid
|
|
80
|
+
*/
|
|
81
|
+
export interface IdValidationResult {
|
|
82
|
+
valid: boolean;
|
|
83
|
+
reason?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Validate task ID with detailed error information
|
|
88
|
+
*/
|
|
89
|
+
export function validateTaskId(id: unknown): IdValidationResult {
|
|
90
|
+
if (id === null || id === undefined) {
|
|
91
|
+
return { valid: false, reason: "Task ID is required" };
|
|
92
|
+
}
|
|
93
|
+
if (typeof id !== "string") {
|
|
94
|
+
return { valid: false, reason: `Task ID must be a string (received ${typeof id})` };
|
|
95
|
+
}
|
|
96
|
+
if (id.length === 0) {
|
|
97
|
+
return { valid: false, reason: "Task ID cannot be empty" };
|
|
98
|
+
}
|
|
99
|
+
if (!id.startsWith("task_")) {
|
|
100
|
+
return { valid: false, reason: "Task ID must start with 'task_' prefix" };
|
|
101
|
+
}
|
|
102
|
+
if (!/^task_[a-z0-9]+$/.test(id)) {
|
|
103
|
+
return { valid: false, reason: "Task ID contains invalid characters" };
|
|
104
|
+
}
|
|
105
|
+
return { valid: true };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Validate project ID with detailed error information
|
|
110
|
+
*/
|
|
111
|
+
export function validateProjectId(id: unknown): IdValidationResult {
|
|
112
|
+
if (id === null || id === undefined) {
|
|
113
|
+
return { valid: false, reason: "Project ID is required" };
|
|
114
|
+
}
|
|
115
|
+
if (typeof id !== "string") {
|
|
116
|
+
return { valid: false, reason: `Project ID must be a string (received ${typeof id})` };
|
|
117
|
+
}
|
|
118
|
+
if (id.length === 0) {
|
|
119
|
+
return { valid: false, reason: "Project ID cannot be empty" };
|
|
120
|
+
}
|
|
121
|
+
if (!id.startsWith("proj_")) {
|
|
122
|
+
return { valid: false, reason: "Project ID must start with 'proj_' prefix" };
|
|
123
|
+
}
|
|
124
|
+
if (!/^proj_[a-z0-9]+$/.test(id)) {
|
|
125
|
+
return { valid: false, reason: "Project ID contains invalid characters" };
|
|
126
|
+
}
|
|
127
|
+
return { valid: true };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Validate inbox ID with detailed error information
|
|
132
|
+
*/
|
|
133
|
+
export function validateInboxId(id: unknown): IdValidationResult {
|
|
134
|
+
if (id === null || id === undefined) {
|
|
135
|
+
return { valid: false, reason: "Inbox ID is required" };
|
|
136
|
+
}
|
|
137
|
+
if (typeof id !== "string") {
|
|
138
|
+
return { valid: false, reason: `Inbox ID must be a string (received ${typeof id})` };
|
|
139
|
+
}
|
|
140
|
+
if (id.length === 0) {
|
|
141
|
+
return { valid: false, reason: "Inbox ID cannot be empty" };
|
|
142
|
+
}
|
|
143
|
+
if (!id.startsWith("inbox_")) {
|
|
144
|
+
return { valid: false, reason: "Inbox ID must start with 'inbox_' prefix" };
|
|
145
|
+
}
|
|
146
|
+
if (!/^inbox_[a-z0-9]+$/.test(id)) {
|
|
147
|
+
return { valid: false, reason: "Inbox ID contains invalid characters" };
|
|
148
|
+
}
|
|
149
|
+
return { valid: true };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Assert valid task ID, throw if invalid
|
|
154
|
+
*/
|
|
155
|
+
export function assertValidTaskId(id: unknown): asserts id is string {
|
|
156
|
+
const result = validateTaskId(id);
|
|
157
|
+
if (!result.valid) {
|
|
158
|
+
throw new InvalidIdError("task", id, result.reason!);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Assert valid project ID, throw if invalid
|
|
164
|
+
*/
|
|
165
|
+
export function assertValidProjectId(id: unknown): asserts id is string {
|
|
166
|
+
const result = validateProjectId(id);
|
|
167
|
+
if (!result.valid) {
|
|
168
|
+
throw new InvalidIdError("project", id, result.reason!);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Assert valid inbox ID, throw if invalid
|
|
174
|
+
*/
|
|
175
|
+
export function assertValidInboxId(id: unknown): asserts id is string {
|
|
176
|
+
const result = validateInboxId(id);
|
|
177
|
+
if (!result.valid) {
|
|
178
|
+
throw new InvalidIdError("inbox", id, result.reason!);
|
|
179
|
+
}
|
|
180
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
generateId,
|
|
3
|
+
generateTaskId,
|
|
4
|
+
generateProjectId,
|
|
5
|
+
generateViewId,
|
|
6
|
+
isValidProjectId,
|
|
7
|
+
isValidTaskId,
|
|
8
|
+
generateInboxId,
|
|
9
|
+
isValidInboxId,
|
|
10
|
+
// Validation utilities with detailed error information
|
|
11
|
+
InvalidIdError,
|
|
12
|
+
type IdValidationResult,
|
|
13
|
+
validateTaskId,
|
|
14
|
+
validateProjectId,
|
|
15
|
+
validateInboxId,
|
|
16
|
+
assertValidTaskId,
|
|
17
|
+
assertValidProjectId,
|
|
18
|
+
assertValidInboxId,
|
|
19
|
+
} from "./id.js";
|
|
2
20
|
export { now, parseRelativeDate, formatDate, formatDisplayDate, isToday, isPastDue, isWithinDays } from "./date.js";
|
|
3
|
-
export { parseTaskInput } from "./natural-language.js";
|
|
21
|
+
export { parseTaskInput, parseInboxInput, parseInput, type ParseTarget, type ParsedInput } from "./natural-language.js";
|
|
22
|
+
export {
|
|
23
|
+
MAX_HIERARCHY_DEPTH,
|
|
24
|
+
getTaskLevel,
|
|
25
|
+
validateHierarchyDepth,
|
|
26
|
+
getAncestorIds,
|
|
27
|
+
getDescendantIds,
|
|
28
|
+
getChildTasks,
|
|
29
|
+
getRootTask,
|
|
30
|
+
buildTaskTree,
|
|
31
|
+
type TaskTreeNode,
|
|
32
|
+
} from "./hierarchy.js";
|
|
33
|
+
|
|
34
|
+
// Projection utilities (token optimization)
|
|
35
|
+
export {
|
|
36
|
+
projectTask,
|
|
37
|
+
projectTasks,
|
|
38
|
+
projectTasksPaginated,
|
|
39
|
+
projectProject,
|
|
40
|
+
projectProjects,
|
|
41
|
+
projectInboxItem,
|
|
42
|
+
projectInboxItems,
|
|
43
|
+
formatResponse,
|
|
44
|
+
applyPagination,
|
|
45
|
+
truncate,
|
|
46
|
+
summarizeList,
|
|
47
|
+
} from "./projection.js";
|
|
@@ -1,6 +1,118 @@
|
|
|
1
1
|
import type { Priority, TaskCreateInput } from "../schemas/task.js";
|
|
2
|
+
import type { InboxCreateInput } from "../schemas/inbox.js";
|
|
2
3
|
import { parseRelativeDate, formatDate } from "./date.js";
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Maximum allowed input length for natural language parsing.
|
|
7
|
+
* Prevents DoS attacks from extremely long input strings.
|
|
8
|
+
*/
|
|
9
|
+
export const MAX_INPUT_LENGTH = 1000;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Error thrown when input validation fails
|
|
13
|
+
*/
|
|
14
|
+
export class InputValidationError extends Error {
|
|
15
|
+
constructor(message: string) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "InputValidationError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validates input string length
|
|
23
|
+
* @throws {InputValidationError} if input exceeds MAX_INPUT_LENGTH
|
|
24
|
+
*/
|
|
25
|
+
function validateInputLength(input: string): void {
|
|
26
|
+
if (input.length > MAX_INPUT_LENGTH) {
|
|
27
|
+
throw new InputValidationError(
|
|
28
|
+
`Input exceeds maximum length of ${MAX_INPUT_LENGTH} characters (received ${input.length})`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Target type for parsed input
|
|
35
|
+
*/
|
|
36
|
+
export type ParseTarget = "task" | "inbox";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extended parse result with target information
|
|
40
|
+
*/
|
|
41
|
+
export interface ParsedInput {
|
|
42
|
+
target: ParseTarget;
|
|
43
|
+
task?: TaskCreateInput;
|
|
44
|
+
inbox?: InboxCreateInput;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse natural language input with target detection
|
|
49
|
+
*
|
|
50
|
+
* Target keywords:
|
|
51
|
+
* - >>inbox, >>메모, >>아이디어 → capture to inbox
|
|
52
|
+
* - >>task, >>태스크 (default) → create task
|
|
53
|
+
*
|
|
54
|
+
* Examples:
|
|
55
|
+
* - "API 리팩토링 아이디어 >>inbox #backend"
|
|
56
|
+
* - "Review PR tomorrow #dev !high" (default: task)
|
|
57
|
+
* - "새 기능 구상 >>메모"
|
|
58
|
+
*/
|
|
59
|
+
export function parseInput(input: string): ParsedInput {
|
|
60
|
+
validateInputLength(input);
|
|
61
|
+
let remaining = input.trim();
|
|
62
|
+
let target: ParseTarget = "task";
|
|
63
|
+
|
|
64
|
+
// Detect target keywords (>>inbox, >>task, >>메모, >>아이디어, >>태스크)
|
|
65
|
+
const targetMatch = remaining.match(/>>(inbox|task|메모|아이디어|태스크)/i);
|
|
66
|
+
if (targetMatch) {
|
|
67
|
+
const keyword = targetMatch[1]!.toLowerCase();
|
|
68
|
+
if (keyword === "inbox" || keyword === "메모" || keyword === "아이디어") {
|
|
69
|
+
target = "inbox";
|
|
70
|
+
} else {
|
|
71
|
+
target = "task";
|
|
72
|
+
}
|
|
73
|
+
remaining = remaining.replace(targetMatch[0], "").trim();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (target === "inbox") {
|
|
77
|
+
return {
|
|
78
|
+
target: "inbox",
|
|
79
|
+
inbox: parseInboxInput(remaining),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
target: "task",
|
|
85
|
+
task: parseTaskInput(remaining),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Parse natural language inbox input
|
|
91
|
+
*
|
|
92
|
+
* Examples:
|
|
93
|
+
* - "GraphQL 도입 검토 #backend #연구"
|
|
94
|
+
* - "성능 개선 아이디어 #performance"
|
|
95
|
+
*/
|
|
96
|
+
export function parseInboxInput(input: string): InboxCreateInput {
|
|
97
|
+
validateInputLength(input);
|
|
98
|
+
let remaining = input.trim();
|
|
99
|
+
const result: InboxCreateInput = { content: "" };
|
|
100
|
+
|
|
101
|
+
// Extract tags (#dev, #backend, #개발)
|
|
102
|
+
const tagMatches = remaining.match(/#([\p{L}\p{N}_]+)/gu);
|
|
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
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Clean up and set content
|
|
111
|
+
result.content = remaining.replace(/\s+/g, " ").trim();
|
|
112
|
+
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
4
116
|
/**
|
|
5
117
|
* Parse natural language task input
|
|
6
118
|
*
|
|
@@ -11,6 +123,7 @@ import { parseRelativeDate, formatDate } from "./date.js";
|
|
|
11
123
|
* - "Write tests every Monday #testing"
|
|
12
124
|
*/
|
|
13
125
|
export function parseTaskInput(input: string): TaskCreateInput {
|
|
126
|
+
validateInputLength(input);
|
|
14
127
|
let remaining = input.trim();
|
|
15
128
|
const result: TaskCreateInput = { title: "" };
|
|
16
129
|
|