@task-mcp/shared 1.0.4 → 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.
Files changed (88) hide show
  1. package/dist/algorithms/critical-path.d.ts.map +1 -1
  2. package/dist/algorithms/critical-path.js +50 -26
  3. package/dist/algorithms/critical-path.js.map +1 -1
  4. package/dist/algorithms/dependency-integrity.d.ts +73 -0
  5. package/dist/algorithms/dependency-integrity.d.ts.map +1 -0
  6. package/dist/algorithms/dependency-integrity.js +189 -0
  7. package/dist/algorithms/dependency-integrity.js.map +1 -0
  8. package/dist/algorithms/index.d.ts +2 -0
  9. package/dist/algorithms/index.d.ts.map +1 -1
  10. package/dist/algorithms/index.js +2 -0
  11. package/dist/algorithms/index.js.map +1 -1
  12. package/dist/algorithms/tech-analysis.d.ts +106 -0
  13. package/dist/algorithms/tech-analysis.d.ts.map +1 -0
  14. package/dist/algorithms/tech-analysis.js +296 -0
  15. package/dist/algorithms/tech-analysis.js.map +1 -0
  16. package/dist/algorithms/tech-analysis.test.d.ts +2 -0
  17. package/dist/algorithms/tech-analysis.test.d.ts.map +1 -0
  18. package/dist/algorithms/tech-analysis.test.js +338 -0
  19. package/dist/algorithms/tech-analysis.test.js.map +1 -0
  20. package/dist/algorithms/topological-sort.d.ts.map +1 -1
  21. package/dist/algorithms/topological-sort.js +60 -8
  22. package/dist/algorithms/topological-sort.js.map +1 -1
  23. package/dist/schemas/inbox.d.ts +24 -0
  24. package/dist/schemas/inbox.d.ts.map +1 -0
  25. package/dist/schemas/inbox.js +25 -0
  26. package/dist/schemas/inbox.js.map +1 -0
  27. package/dist/schemas/index.d.ts +3 -1
  28. package/dist/schemas/index.d.ts.map +1 -1
  29. package/dist/schemas/index.js +9 -1
  30. package/dist/schemas/index.js.map +1 -1
  31. package/dist/schemas/response-format.d.ts +79 -0
  32. package/dist/schemas/response-format.d.ts.map +1 -0
  33. package/dist/schemas/response-format.js +17 -0
  34. package/dist/schemas/response-format.js.map +1 -0
  35. package/dist/schemas/task.d.ts +57 -0
  36. package/dist/schemas/task.d.ts.map +1 -1
  37. package/dist/schemas/task.js +34 -0
  38. package/dist/schemas/task.js.map +1 -1
  39. package/dist/utils/date.d.ts.map +1 -1
  40. package/dist/utils/date.js +17 -2
  41. package/dist/utils/date.js.map +1 -1
  42. package/dist/utils/hierarchy.d.ts +75 -0
  43. package/dist/utils/hierarchy.d.ts.map +1 -0
  44. package/dist/utils/hierarchy.js +179 -0
  45. package/dist/utils/hierarchy.js.map +1 -0
  46. package/dist/utils/id.d.ts +51 -1
  47. package/dist/utils/id.d.ts.map +1 -1
  48. package/dist/utils/id.js +124 -4
  49. package/dist/utils/id.js.map +1 -1
  50. package/dist/utils/id.test.d.ts +2 -0
  51. package/dist/utils/id.test.d.ts.map +1 -0
  52. package/dist/utils/id.test.js +228 -0
  53. package/dist/utils/id.test.js.map +1 -0
  54. package/dist/utils/index.d.ts +4 -2
  55. package/dist/utils/index.d.ts.map +1 -1
  56. package/dist/utils/index.js +7 -2
  57. package/dist/utils/index.js.map +1 -1
  58. package/dist/utils/natural-language.d.ts +45 -0
  59. package/dist/utils/natural-language.d.ts.map +1 -1
  60. package/dist/utils/natural-language.js +86 -0
  61. package/dist/utils/natural-language.js.map +1 -1
  62. package/dist/utils/projection.d.ts +65 -0
  63. package/dist/utils/projection.d.ts.map +1 -0
  64. package/dist/utils/projection.js +181 -0
  65. package/dist/utils/projection.js.map +1 -0
  66. package/dist/utils/projection.test.d.ts +2 -0
  67. package/dist/utils/projection.test.d.ts.map +1 -0
  68. package/dist/utils/projection.test.js +400 -0
  69. package/dist/utils/projection.test.js.map +1 -0
  70. package/package.json +1 -1
  71. package/src/algorithms/critical-path.ts +56 -24
  72. package/src/algorithms/dependency-integrity.ts +270 -0
  73. package/src/algorithms/index.ts +28 -0
  74. package/src/algorithms/tech-analysis.test.ts +413 -0
  75. package/src/algorithms/tech-analysis.ts +412 -0
  76. package/src/algorithms/topological-sort.ts +66 -9
  77. package/src/schemas/inbox.ts +32 -0
  78. package/src/schemas/index.ts +31 -0
  79. package/src/schemas/response-format.ts +108 -0
  80. package/src/schemas/task.ts +50 -0
  81. package/src/utils/date.ts +18 -2
  82. package/src/utils/hierarchy.ts +224 -0
  83. package/src/utils/id.test.ts +281 -0
  84. package/src/utils/id.ts +139 -4
  85. package/src/utils/index.ts +46 -2
  86. package/src/utils/natural-language.ts +113 -0
  87. package/src/utils/projection.test.ts +505 -0
  88. 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
- const timestamp = Date.now().toString(36);
6
- const random = Math.random().toString(36).substring(2, 8);
7
- return prefix ? `${prefix}_${timestamp}${random}` : `${timestamp}${random}`;
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
+ }
@@ -1,3 +1,47 @@
1
- export { generateId, generateTaskId, generateProjectId, generateViewId, isValidProjectId, isValidTaskId } from "./id.js";
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