@task-mcp/shared 1.0.29 → 1.0.30

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 (68) hide show
  1. package/dist/schemas/inbox.d.ts +2 -2
  2. package/dist/schemas/response-format.d.ts +11 -0
  3. package/dist/schemas/response-format.d.ts.map +1 -1
  4. package/dist/schemas/response-format.js.map +1 -1
  5. package/dist/schemas/session.d.ts +12 -12
  6. package/dist/schemas/state.d.ts +2 -2
  7. package/dist/schemas/task.d.ts +9 -0
  8. package/dist/schemas/task.d.ts.map +1 -1
  9. package/dist/schemas/task.js +3 -0
  10. package/dist/schemas/task.js.map +1 -1
  11. package/dist/utils/clustering.d.ts +60 -0
  12. package/dist/utils/clustering.d.ts.map +1 -0
  13. package/dist/utils/clustering.js +283 -0
  14. package/dist/utils/clustering.js.map +1 -0
  15. package/dist/utils/clustering.test.d.ts +2 -0
  16. package/dist/utils/clustering.test.d.ts.map +1 -0
  17. package/dist/utils/clustering.test.js +237 -0
  18. package/dist/utils/clustering.test.js.map +1 -0
  19. package/dist/utils/env.d.ts +24 -0
  20. package/dist/utils/env.d.ts.map +1 -0
  21. package/dist/utils/env.js +40 -0
  22. package/dist/utils/env.js.map +1 -0
  23. package/dist/utils/hierarchy.d.ts.map +1 -1
  24. package/dist/utils/hierarchy.js +7 -3
  25. package/dist/utils/hierarchy.js.map +1 -1
  26. package/dist/utils/index.d.ts +4 -1
  27. package/dist/utils/index.d.ts.map +1 -1
  28. package/dist/utils/index.js +7 -1
  29. package/dist/utils/index.js.map +1 -1
  30. package/dist/utils/intent-extractor.d.ts +30 -0
  31. package/dist/utils/intent-extractor.d.ts.map +1 -0
  32. package/dist/utils/intent-extractor.js +135 -0
  33. package/dist/utils/intent-extractor.js.map +1 -0
  34. package/dist/utils/intent-extractor.test.d.ts +2 -0
  35. package/dist/utils/intent-extractor.test.d.ts.map +1 -0
  36. package/dist/utils/intent-extractor.test.js +69 -0
  37. package/dist/utils/intent-extractor.test.js.map +1 -0
  38. package/dist/utils/natural-language.d.ts.map +1 -1
  39. package/dist/utils/natural-language.js +9 -8
  40. package/dist/utils/natural-language.js.map +1 -1
  41. package/dist/utils/natural-language.test.js +22 -0
  42. package/dist/utils/natural-language.test.js.map +1 -1
  43. package/dist/utils/plan-parser.d.ts.map +1 -1
  44. package/dist/utils/plan-parser.js +2 -8
  45. package/dist/utils/plan-parser.js.map +1 -1
  46. package/dist/utils/projection.d.ts.map +1 -1
  47. package/dist/utils/projection.js +43 -1
  48. package/dist/utils/projection.js.map +1 -1
  49. package/dist/utils/projection.test.js +57 -7
  50. package/dist/utils/projection.test.js.map +1 -1
  51. package/dist/utils/terminal-ui.test.js +13 -22
  52. package/dist/utils/terminal-ui.test.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/schemas/response-format.ts +15 -2
  55. package/src/schemas/task.ts +3 -0
  56. package/src/utils/clustering.test.ts +285 -0
  57. package/src/utils/clustering.ts +336 -0
  58. package/src/utils/env.ts +41 -0
  59. package/src/utils/hierarchy.ts +9 -5
  60. package/src/utils/index.ts +17 -0
  61. package/src/utils/intent-extractor.test.ts +84 -0
  62. package/src/utils/intent-extractor.ts +156 -0
  63. package/src/utils/natural-language.test.ts +27 -0
  64. package/src/utils/natural-language.ts +10 -9
  65. package/src/utils/plan-parser.ts +4 -16
  66. package/src/utils/projection.test.ts +61 -7
  67. package/src/utils/projection.ts +44 -1
  68. package/src/utils/terminal-ui.test.ts +13 -22
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Semantic clustering utilities for task similarity
3
+ *
4
+ * Uses lightweight text similarity without external APIs.
5
+ * Designed for session resumption to suggest related tasks.
6
+ */
7
+ /**
8
+ * Stop words to ignore in similarity calculation
9
+ * Common words that don't contribute to semantic meaning
10
+ */
11
+ const STOP_WORDS = new Set([
12
+ // English
13
+ "a",
14
+ "am",
15
+ "an",
16
+ "the",
17
+ "is",
18
+ "are",
19
+ "was",
20
+ "were",
21
+ "be",
22
+ "been",
23
+ "being",
24
+ "have",
25
+ "has",
26
+ "had",
27
+ "do",
28
+ "does",
29
+ "did",
30
+ "will",
31
+ "would",
32
+ "could",
33
+ "should",
34
+ "may",
35
+ "might",
36
+ "must",
37
+ "can",
38
+ "to",
39
+ "of",
40
+ "in",
41
+ "for",
42
+ "on",
43
+ "with",
44
+ "at",
45
+ "by",
46
+ "from",
47
+ "as",
48
+ "into",
49
+ "through",
50
+ "during",
51
+ "before",
52
+ "after",
53
+ "above",
54
+ "below",
55
+ "and",
56
+ "or",
57
+ "but",
58
+ "if",
59
+ "then",
60
+ "else",
61
+ "when",
62
+ "where",
63
+ "why",
64
+ "how",
65
+ "all",
66
+ "each",
67
+ "every",
68
+ "both",
69
+ "few",
70
+ "more",
71
+ "most",
72
+ "other",
73
+ "some",
74
+ "such",
75
+ "no",
76
+ "nor",
77
+ "not",
78
+ "only",
79
+ "own",
80
+ "same",
81
+ "so",
82
+ "than",
83
+ "too",
84
+ "very",
85
+ "just",
86
+ "also",
87
+ "this",
88
+ "that",
89
+ "these",
90
+ "those",
91
+ "it",
92
+ "its",
93
+ // Korean particles (common suffixes)
94
+ "을",
95
+ "를",
96
+ "이",
97
+ "가",
98
+ "은",
99
+ "는",
100
+ "에",
101
+ "에서",
102
+ "으로",
103
+ "로",
104
+ "와",
105
+ "과",
106
+ "의",
107
+ "도",
108
+ "만",
109
+ "까지",
110
+ "부터",
111
+ "하다",
112
+ "하기",
113
+ "해서",
114
+ "하고",
115
+ ]);
116
+ /**
117
+ * Tokenize text into meaningful words
118
+ * - Converts to lowercase
119
+ * - Splits on whitespace and punctuation
120
+ * - Removes stop words
121
+ * - Removes short tokens (< 2 chars)
122
+ */
123
+ export function tokenize(text) {
124
+ if (!text)
125
+ return [];
126
+ return text
127
+ .toLowerCase()
128
+ .split(/[\s\-_.,;:!?()\[\]{}'"\/\\]+/)
129
+ .filter((token) => token.length >= 2 && !STOP_WORDS.has(token));
130
+ }
131
+ /**
132
+ * Calculate Jaccard similarity between two token sets
133
+ * Returns value between 0 (no overlap) and 1 (identical)
134
+ */
135
+ export function jaccardSimilarity(tokensA, tokensB) {
136
+ if (tokensA.length === 0 && tokensB.length === 0)
137
+ return 0;
138
+ if (tokensA.length === 0 || tokensB.length === 0)
139
+ return 0;
140
+ const setA = new Set(tokensA);
141
+ const setB = new Set(tokensB);
142
+ let intersection = 0;
143
+ for (const token of setA) {
144
+ if (setB.has(token)) {
145
+ intersection++;
146
+ }
147
+ }
148
+ const union = setA.size + setB.size - intersection;
149
+ return union === 0 ? 0 : intersection / union;
150
+ }
151
+ /**
152
+ * Calculate weighted similarity between two tasks
153
+ *
154
+ * Weights:
155
+ * - Intent: 0.4 (highest priority - captures purpose)
156
+ * - Title: 0.3 (strong signal)
157
+ * - Description: 0.2 (additional context)
158
+ * - Tags: 0.1 (explicit categorization)
159
+ */
160
+ export function calculateTaskSimilarity(task1, task2) {
161
+ // Extract text fields
162
+ const intent1 = task1.workContext?.intent || "";
163
+ const intent2 = task2.workContext?.intent || "";
164
+ const title1 = task1.title || "";
165
+ const title2 = task2.title || "";
166
+ const desc1 = task1.description || "";
167
+ const desc2 = task2.description || "";
168
+ const tags1 = task1.tags?.join(" ") || "";
169
+ const tags2 = task2.tags?.join(" ") || "";
170
+ // Tokenize
171
+ const intentTokens1 = tokenize(intent1);
172
+ const intentTokens2 = tokenize(intent2);
173
+ const titleTokens1 = tokenize(title1);
174
+ const titleTokens2 = tokenize(title2);
175
+ const descTokens1 = tokenize(desc1);
176
+ const descTokens2 = tokenize(desc2);
177
+ const tagTokens1 = tokenize(tags1);
178
+ const tagTokens2 = tokenize(tags2);
179
+ // Calculate component similarities
180
+ const intentSim = jaccardSimilarity(intentTokens1, intentTokens2);
181
+ const titleSim = jaccardSimilarity(titleTokens1, titleTokens2);
182
+ const descSim = jaccardSimilarity(descTokens1, descTokens2);
183
+ const tagSim = jaccardSimilarity(tagTokens1, tagTokens2);
184
+ // Weighted combination
185
+ // Adjust weights based on available content
186
+ let totalWeight = 0;
187
+ let weightedSum = 0;
188
+ if (intentTokens1.length > 0 || intentTokens2.length > 0) {
189
+ weightedSum += intentSim * 0.4;
190
+ totalWeight += 0.4;
191
+ }
192
+ if (titleTokens1.length > 0 || titleTokens2.length > 0) {
193
+ weightedSum += titleSim * 0.3;
194
+ totalWeight += 0.3;
195
+ }
196
+ if (descTokens1.length > 0 || descTokens2.length > 0) {
197
+ weightedSum += descSim * 0.2;
198
+ totalWeight += 0.2;
199
+ }
200
+ if (tagTokens1.length > 0 || tagTokens2.length > 0) {
201
+ weightedSum += tagSim * 0.1;
202
+ totalWeight += 0.1;
203
+ }
204
+ return totalWeight === 0 ? 0 : weightedSum / totalWeight;
205
+ }
206
+ /**
207
+ * Find tasks related to a given task
208
+ *
209
+ * @param task - The reference task
210
+ * @param allTasks - All tasks to search from
211
+ * @param options - Configuration options
212
+ * @returns Array of related tasks sorted by similarity (highest first)
213
+ */
214
+ export function findRelatedTasks(task, allTasks, options = {}) {
215
+ const { limit = 5, minSimilarity = 0.1, excludeCompleted = true, excludeSameParent = true, } = options;
216
+ const candidates = allTasks.filter((t) => {
217
+ // Exclude self
218
+ if (t.id === task.id)
219
+ return false;
220
+ // Exclude completed if requested
221
+ if (excludeCompleted && t.status === "completed")
222
+ return false;
223
+ // Exclude same parent (siblings are already contextually related)
224
+ if (excludeSameParent && task.parentId && t.parentId === task.parentId)
225
+ return false;
226
+ // Exclude direct parent/child relationships
227
+ if (t.parentId === task.id || task.parentId === t.id)
228
+ return false;
229
+ return true;
230
+ });
231
+ // Calculate similarities
232
+ const withSimilarity = candidates
233
+ .map((t) => ({
234
+ task: t,
235
+ similarity: calculateTaskSimilarity(task, t),
236
+ }))
237
+ .filter((item) => item.similarity >= minSimilarity)
238
+ .sort((a, b) => b.similarity - a.similarity)
239
+ .slice(0, limit);
240
+ return withSimilarity;
241
+ }
242
+ /**
243
+ * Cluster tasks by similarity
244
+ * Returns groups of related tasks
245
+ */
246
+ export function clusterTasks(tasks, options = {}) {
247
+ const { minClusterSize = 2, similarityThreshold = 0.2 } = options;
248
+ // Simple greedy clustering
249
+ const assigned = new Set();
250
+ const clusters = [];
251
+ // Sort by priority to use higher priority tasks as cluster representatives
252
+ const sortedTasks = [...tasks].sort((a, b) => {
253
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
254
+ return (priorityOrder[a.priority] ?? 2) - (priorityOrder[b.priority] ?? 2);
255
+ });
256
+ for (const task of sortedTasks) {
257
+ if (assigned.has(task.id))
258
+ continue;
259
+ // Find related tasks that haven't been assigned
260
+ const related = findRelatedTasks(task, tasks, {
261
+ minSimilarity: similarityThreshold,
262
+ excludeCompleted: false,
263
+ excludeSameParent: false,
264
+ limit: 10,
265
+ }).filter((r) => !assigned.has(r.task.id));
266
+ if (related.length >= minClusterSize - 1) {
267
+ // Create cluster
268
+ const members = [task, ...related.map((r) => r.task)];
269
+ const avgSimilarity = related.length > 0 ? related.reduce((sum, r) => sum + r.similarity, 0) / related.length : 0;
270
+ clusters.push({
271
+ representative: task,
272
+ members,
273
+ avgSimilarity,
274
+ });
275
+ // Mark as assigned
276
+ for (const member of members) {
277
+ assigned.add(member.id);
278
+ }
279
+ }
280
+ }
281
+ return clusters;
282
+ }
283
+ //# sourceMappingURL=clustering.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clustering.js","sourceRoot":"","sources":["../../src/utils/clustering.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=clustering.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clustering.test.d.ts","sourceRoot":"","sources":["../../src/utils/clustering.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,237 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { tokenize, jaccardSimilarity, calculateTaskSimilarity, findRelatedTasks, clusterTasks, } from "./clustering.js";
3
+ // Helper to create minimal task for testing
4
+ function createTask(overrides) {
5
+ return {
6
+ status: "pending",
7
+ priority: "medium",
8
+ workspace: "test-workspace",
9
+ tags: [],
10
+ contexts: [],
11
+ createdAt: new Date().toISOString(),
12
+ updatedAt: new Date().toISOString(),
13
+ ...overrides,
14
+ };
15
+ }
16
+ describe("tokenize", () => {
17
+ test("splits on whitespace and punctuation", () => {
18
+ expect(tokenize("Fix the bug")).toEqual(["fix", "bug"]);
19
+ expect(tokenize("user-authentication")).toEqual(["user", "authentication"]);
20
+ expect(tokenize("task.create")).toEqual(["task", "create"]);
21
+ });
22
+ test("removes stop words", () => {
23
+ expect(tokenize("the user is authenticated")).toEqual(["user", "authenticated"]);
24
+ expect(tokenize("to be or not to be")).toEqual([]);
25
+ });
26
+ test("removes short tokens (< 2 chars)", () => {
27
+ // "I" and "a" are 1 char, removed. "am" is a stop word
28
+ expect(tokenize("I am a user")).toEqual(["user"]);
29
+ });
30
+ test("handles Korean text", () => {
31
+ expect(tokenize("버그 수정하기")).toEqual(["버그", "수정하기"]);
32
+ // Korean particles are removed
33
+ expect(tokenize("사용자를")).toEqual(["사용자를"]); // 를 is suffix, not separate word
34
+ });
35
+ test("handles empty input", () => {
36
+ expect(tokenize("")).toEqual([]);
37
+ expect(tokenize(" ")).toEqual([]);
38
+ });
39
+ });
40
+ describe("jaccardSimilarity", () => {
41
+ test("returns 1 for identical sets", () => {
42
+ expect(jaccardSimilarity(["fix", "bug"], ["fix", "bug"])).toBe(1);
43
+ });
44
+ test("returns 0 for completely different sets", () => {
45
+ expect(jaccardSimilarity(["fix", "bug"], ["create", "feature"])).toBe(0);
46
+ });
47
+ test("returns 0.5 for half overlap", () => {
48
+ expect(jaccardSimilarity(["fix", "bug"], ["fix", "error"])).toBeCloseTo(0.333, 2);
49
+ });
50
+ test("handles empty arrays", () => {
51
+ expect(jaccardSimilarity([], [])).toBe(0);
52
+ expect(jaccardSimilarity(["fix"], [])).toBe(0);
53
+ });
54
+ });
55
+ describe("calculateTaskSimilarity", () => {
56
+ test("returns high similarity for tasks with same intent", () => {
57
+ const task1 = createTask({
58
+ id: "1",
59
+ title: "Fix login bug",
60
+ workContext: { intent: "Fix issue or bug" },
61
+ });
62
+ const task2 = createTask({
63
+ id: "2",
64
+ title: "Debug authentication",
65
+ workContext: { intent: "Fix issue or bug" },
66
+ });
67
+ const similarity = calculateTaskSimilarity(task1, task2);
68
+ expect(similarity).toBeGreaterThan(0.3);
69
+ });
70
+ test("returns high similarity for tasks with similar titles", () => {
71
+ const task1 = createTask({
72
+ id: "1",
73
+ title: "Implement user authentication",
74
+ });
75
+ const task2 = createTask({
76
+ id: "2",
77
+ title: "Implement admin authentication",
78
+ });
79
+ const similarity = calculateTaskSimilarity(task1, task2);
80
+ expect(similarity).toBeGreaterThan(0.3);
81
+ });
82
+ test("returns low similarity for unrelated tasks", () => {
83
+ const task1 = createTask({
84
+ id: "1",
85
+ title: "Fix login bug",
86
+ workContext: { intent: "Fix issue or bug" },
87
+ });
88
+ const task2 = createTask({
89
+ id: "2",
90
+ title: "Write documentation",
91
+ workContext: { intent: "Document functionality" },
92
+ });
93
+ const similarity = calculateTaskSimilarity(task1, task2);
94
+ expect(similarity).toBeLessThan(0.2);
95
+ });
96
+ test("handles tasks with missing fields", () => {
97
+ const task1 = createTask({ id: "1", title: "Task one" });
98
+ const task2 = createTask({ id: "2", title: "Task two" });
99
+ // Should not throw
100
+ const similarity = calculateTaskSimilarity(task1, task2);
101
+ expect(similarity).toBeGreaterThanOrEqual(0);
102
+ });
103
+ });
104
+ describe("findRelatedTasks", () => {
105
+ const tasks = [
106
+ createTask({
107
+ id: "auth-1",
108
+ title: "Implement user authentication",
109
+ workContext: { intent: "Implement new functionality" },
110
+ }),
111
+ createTask({
112
+ id: "auth-2",
113
+ title: "Implement OAuth authentication",
114
+ workContext: { intent: "Implement new functionality" },
115
+ }),
116
+ createTask({
117
+ id: "bug-1",
118
+ title: "Fix login bug",
119
+ workContext: { intent: "Fix issue or bug" },
120
+ }),
121
+ createTask({
122
+ id: "doc-1",
123
+ title: "Write API documentation",
124
+ workContext: { intent: "Document functionality" },
125
+ }),
126
+ createTask({
127
+ id: "completed-1",
128
+ title: "Implement session authentication",
129
+ status: "completed",
130
+ workContext: { intent: "Implement new functionality" },
131
+ }),
132
+ ];
133
+ test("finds related tasks by similarity", () => {
134
+ const related = findRelatedTasks(tasks[0], tasks);
135
+ // auth-2 should be most similar to auth-1
136
+ expect(related.length).toBeGreaterThan(0);
137
+ expect(related[0].task.id).toBe("auth-2");
138
+ });
139
+ test("excludes completed tasks by default", () => {
140
+ const related = findRelatedTasks(tasks[0], tasks);
141
+ const hasCompleted = related.some((r) => r.task.status === "completed");
142
+ expect(hasCompleted).toBe(false);
143
+ });
144
+ test("includes completed tasks when requested", () => {
145
+ const related = findRelatedTasks(tasks[0], tasks, { excludeCompleted: false });
146
+ const hasCompleted = related.some((r) => r.task.status === "completed");
147
+ expect(hasCompleted).toBe(true);
148
+ });
149
+ test("respects limit option", () => {
150
+ const related = findRelatedTasks(tasks[0], tasks, { limit: 1 });
151
+ expect(related.length).toBe(1);
152
+ });
153
+ test("excludes self", () => {
154
+ const related = findRelatedTasks(tasks[0], tasks);
155
+ const hasSelf = related.some((r) => r.task.id === tasks[0].id);
156
+ expect(hasSelf).toBe(false);
157
+ });
158
+ test("excludes siblings when excludeSameParent is true", () => {
159
+ const parentId = "parent-1";
160
+ const tasksWithParent = [
161
+ createTask({ id: "child-1", title: "Task one", parentId }),
162
+ createTask({ id: "child-2", title: "Task one similar", parentId }),
163
+ createTask({ id: "other", title: "Task one other" }),
164
+ ];
165
+ const related = findRelatedTasks(tasksWithParent[0], tasksWithParent, {
166
+ excludeSameParent: true,
167
+ });
168
+ const hasSibling = related.some((r) => r.task.id === "child-2");
169
+ expect(hasSibling).toBe(false);
170
+ });
171
+ });
172
+ describe("clusterTasks", () => {
173
+ test("groups similar tasks together", () => {
174
+ const tasks = [
175
+ createTask({
176
+ id: "auth-1",
177
+ title: "Implement user authentication",
178
+ priority: "high",
179
+ workContext: { intent: "Implement new functionality" },
180
+ }),
181
+ createTask({
182
+ id: "auth-2",
183
+ title: "Implement OAuth authentication",
184
+ priority: "medium",
185
+ workContext: { intent: "Implement new functionality" },
186
+ }),
187
+ createTask({
188
+ id: "bug-1",
189
+ title: "Fix critical login bug",
190
+ priority: "critical",
191
+ workContext: { intent: "Fix issue or bug" },
192
+ }),
193
+ createTask({
194
+ id: "bug-2",
195
+ title: "Fix authentication bug",
196
+ priority: "high",
197
+ workContext: { intent: "Fix issue or bug" },
198
+ }),
199
+ ];
200
+ const clusters = clusterTasks(tasks, { minClusterSize: 2 });
201
+ // Should have at least one cluster
202
+ expect(clusters.length).toBeGreaterThan(0);
203
+ // Each cluster should have at least minClusterSize members
204
+ for (const cluster of clusters) {
205
+ expect(cluster.members.length).toBeGreaterThanOrEqual(2);
206
+ }
207
+ });
208
+ test("uses higher priority tasks as representatives", () => {
209
+ const tasks = [
210
+ createTask({
211
+ id: "low",
212
+ title: "Implement feature",
213
+ priority: "low",
214
+ workContext: { intent: "Implement new functionality" },
215
+ }),
216
+ createTask({
217
+ id: "high",
218
+ title: "Implement similar feature",
219
+ priority: "high",
220
+ workContext: { intent: "Implement new functionality" },
221
+ }),
222
+ ];
223
+ const clusters = clusterTasks(tasks, { minClusterSize: 2, similarityThreshold: 0.1 });
224
+ if (clusters.length > 0) {
225
+ expect(clusters[0].representative.id).toBe("high");
226
+ }
227
+ });
228
+ test("returns empty array for no matching clusters", () => {
229
+ const tasks = [
230
+ createTask({ id: "1", title: "Unique task one" }),
231
+ createTask({ id: "2", title: "Different topic completely" }),
232
+ ];
233
+ const clusters = clusterTasks(tasks, { minClusterSize: 2, similarityThreshold: 0.5 });
234
+ expect(clusters.length).toBe(0);
235
+ });
236
+ });
237
+ //# sourceMappingURL=clustering.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clustering.test.js","sourceRoot":"","sources":["../../src/utils/clustering.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Environment detection utilities
3
+ */
4
+ /**
5
+ * Check if running in test environment.
6
+ * Detects: NODE_ENV=test, BUN_TEST (set by bun test), VITEST, JEST_WORKER_ID
7
+ */
8
+ export declare function isTestEnv(): boolean;
9
+ /**
10
+ * Check if debug mode is enabled via environment variable
11
+ */
12
+ export declare function isDebugEnabled(): boolean;
13
+ /**
14
+ * Check if verbose logging should be suppressed.
15
+ *
16
+ * Logs are suppressed if:
17
+ * - In test environment AND
18
+ * - TASK_MCP_DEBUG is not set AND
19
+ * - TASK_MCP_TEST_VERBOSE is not set
20
+ *
21
+ * Set TASK_MCP_TEST_VERBOSE=true to enable logs in specific tests.
22
+ */
23
+ export declare function shouldSuppressLogs(): boolean;
24
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/utils/env.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAOnC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAGxC;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAK5C"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Environment detection utilities
3
+ */
4
+ /**
5
+ * Check if running in test environment.
6
+ * Detects: NODE_ENV=test, BUN_TEST (set by bun test), VITEST, JEST_WORKER_ID
7
+ */
8
+ export function isTestEnv() {
9
+ return (process.env.NODE_ENV === "test" ||
10
+ process.env["BUN_TEST"] !== undefined ||
11
+ process.env["VITEST"] !== undefined ||
12
+ process.env["JEST_WORKER_ID"] !== undefined);
13
+ }
14
+ /**
15
+ * Check if debug mode is enabled via environment variable
16
+ */
17
+ export function isDebugEnabled() {
18
+ const debugEnv = process.env["TASK_MCP_DEBUG"];
19
+ return debugEnv === "true" || debugEnv === "1";
20
+ }
21
+ /**
22
+ * Check if verbose logging should be suppressed.
23
+ *
24
+ * Logs are suppressed if:
25
+ * - In test environment AND
26
+ * - TASK_MCP_DEBUG is not set AND
27
+ * - TASK_MCP_TEST_VERBOSE is not set
28
+ *
29
+ * Set TASK_MCP_TEST_VERBOSE=true to enable logs in specific tests.
30
+ */
31
+ export function shouldSuppressLogs() {
32
+ if (!isTestEnv())
33
+ return false;
34
+ if (isDebugEnabled())
35
+ return false;
36
+ if (process.env["TASK_MCP_TEST_VERBOSE"] === "true")
37
+ return false;
38
+ return true;
39
+ }
40
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/utils/env.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM;QAC/B,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,SAAS;QACrC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,SAAS;QACnC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,SAAS,CAC5C,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC/C,OAAO,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,GAAG,CAAC;AACjD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC,SAAS,EAAE;QAAE,OAAO,KAAK,CAAC;IAC/B,IAAI,cAAc,EAAE;QAAE,OAAO,KAAK,CAAC;IACnC,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IAClE,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"hierarchy.d.ts","sourceRoot":"","sources":["../../src/utils/hierarchy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA6BlE;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAU/E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CA0BtE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAyBxE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,CAEnE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAiBtE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,IAAI,EAAE,EACb,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,GAAE,MAAgC,GACzC,YAAY,EAAE,CAkDhB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3D,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,gBAAgB,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,yBAAyB,CAsC1E"}
1
+ {"version":3,"file":"hierarchy.d.ts","sourceRoot":"","sources":["../../src/utils/hierarchy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAG/C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA+BlE;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAU/E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CA0BtE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAyBxE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,CAEnE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAiBtE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,IAAI,EAAE,EACb,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,GAAE,MAAgC,GACzC,YAAY,EAAE,CAmDhB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3D,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,gBAAgB,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,yBAAyB,CAsC1E"}
@@ -1,3 +1,4 @@
1
+ import { shouldSuppressLogs } from "./env.js";
1
2
  /**
2
3
  * Maximum allowed hierarchy depth (0-indexed)
3
4
  * 0 = root task
@@ -28,7 +29,9 @@ export function getTaskLevel(tasks, taskId) {
28
29
  while (currentTask.parentId) {
29
30
  const parent = taskMap.get(currentTask.parentId);
30
31
  if (!parent) {
31
- console.warn(`[task-mcp] Orphaned parent reference: task "${currentTask.id}" references non-existent parent "${currentTask.parentId}"`);
32
+ if (!shouldSuppressLogs()) {
33
+ console.warn(`[task-mcp] Detached parent reference: task "${currentTask.id}" references non-existent parent "${currentTask.parentId}"`);
34
+ }
32
35
  break;
33
36
  }
34
37
  level++;
@@ -186,8 +189,9 @@ export function buildTaskTree(tasks, rootId, maxDepth = MAX_HIERARCHY_DEPTH + 1)
186
189
  }
187
190
  return [buildNode(rootTask, 0, new Set())];
188
191
  }
189
- // Return all root tasks (no parent)
190
- const rootTasks = tasks.filter((t) => !t.parentId);
192
+ // Return all root tasks (no parent OR parent not in filtered tasks)
193
+ // This ensures orphaned subtasks (whose parent was filtered out) are still visible
194
+ const rootTasks = tasks.filter((t) => !t.parentId || !taskMap.has(t.parentId));
191
195
  return rootTasks.map((task) => buildNode(task, 0, new Set()));
192
196
  }
193
197
  /**