@taazkareem/clickup-mcp-server 0.6.5 → 0.6.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/README.md +2 -2
- package/build/server.js +3 -6
- package/build/services/clickup/base.js +156 -45
- package/build/services/clickup/bulk.js +10 -22
- package/build/services/clickup/tag.js +51 -2
- package/build/services/clickup/task/task-core.js +76 -11
- package/build/services/clickup/task/task-search.js +378 -25
- package/build/services/clickup/workspace.js +14 -12
- package/build/tools/tag.js +102 -43
- package/build/tools/task/attachments.js +43 -21
- package/build/tools/task/bulk-operations.js +36 -16
- package/build/tools/task/handlers.js +253 -136
- package/build/tools/task/main.js +9 -33
- package/build/tools/task/single-operations.js +72 -43
- package/build/tools/task/utilities.js +59 -12
- package/build/tools/task/workspace-operations.js +15 -8
- package/build/utils/date-utils.js +7 -4
- package/build/utils/resolver-utils.js +102 -29
- package/package.json +1 -1
|
@@ -52,16 +52,17 @@ export const createTaskTool = {
|
|
|
52
52
|
|
|
53
53
|
Valid Usage:
|
|
54
54
|
1. Provide listId (preferred if available)
|
|
55
|
-
2. Provide listName (will look up the list ID)
|
|
55
|
+
2. Provide listName (system will look up the list ID)
|
|
56
56
|
|
|
57
57
|
Requirements:
|
|
58
58
|
- name: REQUIRED
|
|
59
|
-
-
|
|
59
|
+
- EITHER listId OR listName: REQUIRED
|
|
60
60
|
|
|
61
61
|
Notes:
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
62
|
+
- For multiple tasks, use create_bulk_tasks instead
|
|
63
|
+
- Reuse list IDs from previous responses when possible to avoid redundant lookups
|
|
64
|
+
- To create a subtask, set the parent parameter to the ID of the parent task
|
|
65
|
+
- Custom fields can be set using the custom_fields parameter (array of {id, value} objects)`,
|
|
65
66
|
inputSchema: {
|
|
66
67
|
type: "object",
|
|
67
68
|
properties: {
|
|
@@ -144,17 +145,25 @@ export const updateTaskTool = {
|
|
|
144
145
|
description: `Purpose: Modify properties of an existing task.
|
|
145
146
|
|
|
146
147
|
Valid Usage:
|
|
147
|
-
1. Use taskId (preferred) - works with both regular and custom IDs
|
|
148
|
-
2. Use taskName
|
|
148
|
+
1. Use taskId alone (preferred) - works with both regular and custom IDs
|
|
149
|
+
2. Use taskName alone (will search across all lists)
|
|
150
|
+
3. Use taskName + listName (for faster, targeted search)
|
|
149
151
|
|
|
150
152
|
Requirements:
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
+
- At least one update field (name, description, status, priority, dueDate) must be provided
|
|
154
|
+
- EITHER taskId OR taskName: REQUIRED
|
|
155
|
+
- listName: Optional, but recommended when using taskName
|
|
153
156
|
|
|
154
157
|
Notes:
|
|
158
|
+
- The tool automatically searches for tasks using smart name matching
|
|
159
|
+
- When only taskName is provided, it searches across all lists
|
|
160
|
+
- Adding listName narrows the search to a specific list for better performance
|
|
155
161
|
- Only specified fields will be updated
|
|
156
|
-
- Custom fields can be set using the custom_fields parameter
|
|
157
|
-
|
|
162
|
+
- Custom fields can be set using the custom_fields parameter (array of {id, value} objects)
|
|
163
|
+
|
|
164
|
+
Warning:
|
|
165
|
+
- Using taskName without listName may match multiple tasks
|
|
166
|
+
- If multiple matches are found, the operation will fail with a disambiguation error`,
|
|
158
167
|
inputSchema: {
|
|
159
168
|
type: "object",
|
|
160
169
|
properties: {
|
|
@@ -227,15 +236,16 @@ export const moveTaskTool = {
|
|
|
227
236
|
description: `Purpose: Move a task to a different list.
|
|
228
237
|
|
|
229
238
|
Valid Usage:
|
|
230
|
-
1. Use taskId +
|
|
231
|
-
2. Use taskName + sourceListName +
|
|
239
|
+
1. Use taskId + (listId OR listName) - preferred
|
|
240
|
+
2. Use taskName + sourceListName + (listId OR listName)
|
|
232
241
|
|
|
233
242
|
Requirements:
|
|
234
|
-
-
|
|
235
|
-
-
|
|
243
|
+
- Destination list: EITHER listId OR listName REQUIRED
|
|
244
|
+
- When using taskName, sourceListName is REQUIRED
|
|
236
245
|
|
|
237
246
|
Warning:
|
|
238
|
-
- Task statuses may reset if destination list has different status options
|
|
247
|
+
- Task statuses may reset if destination list has different status options
|
|
248
|
+
- System cannot find a task by name without knowing which list to search in`,
|
|
239
249
|
inputSchema: {
|
|
240
250
|
type: "object",
|
|
241
251
|
properties: {
|
|
@@ -271,15 +281,18 @@ export const duplicateTaskTool = {
|
|
|
271
281
|
description: `Purpose: Create a copy of a task in the same or different list.
|
|
272
282
|
|
|
273
283
|
Valid Usage:
|
|
274
|
-
1. Use taskId (preferred
|
|
275
|
-
2. Use taskName + sourceListName
|
|
284
|
+
1. Use taskId + optional (listId OR listName) - preferred
|
|
285
|
+
2. Use taskName + sourceListName + optional (listId OR listName)
|
|
276
286
|
|
|
277
287
|
Requirements:
|
|
278
|
-
-
|
|
279
|
-
- Destination: OPTIONAL - defaults to original list
|
|
288
|
+
- When using taskName, sourceListName is REQUIRED
|
|
280
289
|
|
|
281
290
|
Notes:
|
|
282
|
-
- The duplicate preserves the original task's properties
|
|
291
|
+
- The duplicate preserves the original task's properties
|
|
292
|
+
- If no destination list specified, uses same list as original task
|
|
293
|
+
|
|
294
|
+
Warning:
|
|
295
|
+
- System cannot find a task by name without knowing which list to search in`,
|
|
283
296
|
inputSchema: {
|
|
284
297
|
type: "object",
|
|
285
298
|
properties: {
|
|
@@ -315,14 +328,20 @@ export const getTaskTool = {
|
|
|
315
328
|
description: `Purpose: Retrieve detailed information about a specific task.
|
|
316
329
|
|
|
317
330
|
Valid Usage:
|
|
318
|
-
1. Use taskId (preferred) - works with both regular and custom IDs
|
|
319
|
-
2. Use taskName
|
|
331
|
+
1. Use taskId alone (preferred) - works with both regular and custom IDs (like "DEV-1234")
|
|
332
|
+
2. Use taskName alone (will search across all lists in the workspace)
|
|
333
|
+
3. Use taskName + listName (for faster, targeted search)
|
|
334
|
+
4. Use customTaskId for explicit custom ID lookup
|
|
320
335
|
|
|
321
336
|
Requirements:
|
|
322
|
-
-
|
|
337
|
+
- EITHER taskId OR taskName OR customTaskId: REQUIRED
|
|
338
|
+
- listName: Optional, but recommended when using taskName for faster and more precise lookup
|
|
323
339
|
|
|
324
|
-
|
|
325
|
-
-
|
|
340
|
+
Note:
|
|
341
|
+
- When using just taskName, the system performs a global search across all lists
|
|
342
|
+
- Task names are most unique within a specific list, so providing listName increases reliability
|
|
343
|
+
- Regular task IDs are always 9 characters long (e.g., "86b394eqa")
|
|
344
|
+
- Custom IDs have an uppercase prefix followed by a hyphen and number (e.g., "DEV-1234")
|
|
326
345
|
- Set subtasks=true to include all subtasks in the response`,
|
|
327
346
|
inputSchema: {
|
|
328
347
|
type: "object",
|
|
@@ -333,11 +352,11 @@ Notes:
|
|
|
333
352
|
},
|
|
334
353
|
taskName: {
|
|
335
354
|
type: "string",
|
|
336
|
-
description: "Name of task to retrieve.
|
|
355
|
+
description: "Name of task to retrieve. Can be used alone for a global search, or with listName for faster lookup."
|
|
337
356
|
},
|
|
338
357
|
listName: {
|
|
339
358
|
type: "string",
|
|
340
|
-
description: "Name of list containing the task.
|
|
359
|
+
description: "Name of list containing the task. Optional but recommended when using taskName."
|
|
341
360
|
},
|
|
342
361
|
customTaskId: {
|
|
343
362
|
type: "string",
|
|
@@ -362,11 +381,12 @@ Valid Usage:
|
|
|
362
381
|
2. Use listName
|
|
363
382
|
|
|
364
383
|
Requirements:
|
|
365
|
-
-
|
|
384
|
+
- EITHER listId OR listName is REQUIRED
|
|
366
385
|
|
|
367
386
|
Notes:
|
|
368
|
-
- Use filters (archived, statuses) to narrow down results
|
|
369
|
-
- Pagination
|
|
387
|
+
- Use filters (archived, statuses, etc.) to narrow down results
|
|
388
|
+
- Pagination available through page parameter
|
|
389
|
+
- Sorting available through order_by and reverse parameters`,
|
|
370
390
|
inputSchema: {
|
|
371
391
|
type: "object",
|
|
372
392
|
properties: {
|
|
@@ -416,14 +436,12 @@ export const getTaskCommentsTool = {
|
|
|
416
436
|
|
|
417
437
|
Valid Usage:
|
|
418
438
|
1. Use taskId (preferred)
|
|
419
|
-
2. Use taskName + listName
|
|
420
|
-
|
|
421
|
-
Requirements:
|
|
422
|
-
- Task identification: EITHER taskId OR taskName REQUIRED
|
|
439
|
+
2. Use taskName + optional listName
|
|
423
440
|
|
|
424
441
|
Notes:
|
|
442
|
+
- If using taskName, providing listName helps locate the correct task
|
|
425
443
|
- Task names may not be unique across different lists
|
|
426
|
-
- Use start
|
|
444
|
+
- Use start and startId parameters for pagination through comments`,
|
|
427
445
|
inputSchema: {
|
|
428
446
|
type: "object",
|
|
429
447
|
properties: {
|
|
@@ -462,11 +480,13 @@ Valid Usage:
|
|
|
462
480
|
2. Use taskName + listName
|
|
463
481
|
|
|
464
482
|
Requirements:
|
|
465
|
-
-
|
|
466
|
-
- commentText
|
|
483
|
+
- EITHER taskId OR (taskName + listName) is REQUIRED
|
|
484
|
+
- commentText is REQUIRED
|
|
467
485
|
|
|
468
486
|
Notes:
|
|
469
|
-
-
|
|
487
|
+
- When using taskName, providing listName helps locate the correct task
|
|
488
|
+
- Set notifyAll to true to send notifications to all task assignees
|
|
489
|
+
- Use assignee to assign the comment to a specific user (optional)`,
|
|
470
490
|
inputSchema: {
|
|
471
491
|
type: "object",
|
|
472
492
|
properties: {
|
|
@@ -506,15 +526,24 @@ export const deleteTaskTool = {
|
|
|
506
526
|
description: `Purpose: PERMANENTLY DELETE a task.
|
|
507
527
|
|
|
508
528
|
Valid Usage:
|
|
509
|
-
1. Use taskId (preferred and safest)
|
|
510
|
-
2. Use taskName
|
|
529
|
+
1. Use taskId alone (preferred and safest)
|
|
530
|
+
2. Use taskName alone (will search across all lists)
|
|
531
|
+
3. Use taskName + listName (for faster, targeted search)
|
|
511
532
|
|
|
512
533
|
Requirements:
|
|
513
|
-
-
|
|
534
|
+
- EITHER taskId OR taskName: REQUIRED
|
|
535
|
+
- listName: Optional, but recommended when using taskName
|
|
536
|
+
|
|
537
|
+
Notes:
|
|
538
|
+
- The tool automatically searches for tasks using smart name matching
|
|
539
|
+
- When only taskName is provided, it searches across all lists
|
|
540
|
+
- Adding listName narrows the search to a specific list for better performance
|
|
541
|
+
- Supports both regular task IDs and custom IDs (like 'DEV-1234')
|
|
514
542
|
|
|
515
543
|
Warning:
|
|
516
544
|
- This action CANNOT be undone
|
|
517
|
-
- Using taskName without listName may match multiple tasks
|
|
545
|
+
- Using taskName without listName may match multiple tasks
|
|
546
|
+
- If multiple matches are found, the operation will fail with a disambiguation error`,
|
|
518
547
|
inputSchema: {
|
|
519
548
|
type: "object",
|
|
520
549
|
properties: {
|
|
@@ -11,7 +11,7 @@ import { formatDueDate } from '../utils.js';
|
|
|
11
11
|
import { clickUpServices } from '../../services/shared.js';
|
|
12
12
|
import { findListIDByName } from '../../tools/list.js';
|
|
13
13
|
// Use shared services instance for ID resolution
|
|
14
|
-
const { workspace: workspaceService } = clickUpServices;
|
|
14
|
+
const { workspace: workspaceService, task: taskService } = clickUpServices;
|
|
15
15
|
//=============================================================================
|
|
16
16
|
// DATA FORMATTING UTILITIES
|
|
17
17
|
//=============================================================================
|
|
@@ -65,22 +65,38 @@ export function formatTaskData(task, additional = {}) {
|
|
|
65
65
|
...additional
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
|
-
//=============================================================================
|
|
69
|
-
// VALIDATION UTILITIES
|
|
70
|
-
//=============================================================================
|
|
71
68
|
/**
|
|
72
69
|
* Validates task identification parameters
|
|
73
|
-
*
|
|
74
|
-
*
|
|
70
|
+
*
|
|
71
|
+
* @param params - Task identification parameters
|
|
72
|
+
* @param options - Validation options
|
|
73
|
+
* @returns Validation result with error message if any
|
|
75
74
|
*/
|
|
76
|
-
export function validateTaskIdentification(
|
|
75
|
+
export function validateTaskIdentification(params, options = {}) {
|
|
76
|
+
const { taskId, taskName, customTaskId, listName } = params;
|
|
77
|
+
const { requireTaskId = false, useGlobalLookup = true } = options;
|
|
78
|
+
// If taskId is required, it must be provided
|
|
79
|
+
if (requireTaskId && !taskId) {
|
|
80
|
+
return {
|
|
81
|
+
isValid: false,
|
|
82
|
+
errorMessage: 'Task ID is required for this operation'
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
// At least one identification method must be provided
|
|
77
86
|
if (!taskId && !taskName && !customTaskId) {
|
|
78
|
-
|
|
87
|
+
return {
|
|
88
|
+
isValid: false,
|
|
89
|
+
errorMessage: 'Either taskId, taskName, or customTaskId must be provided to identify the task'
|
|
90
|
+
};
|
|
79
91
|
}
|
|
80
|
-
// When
|
|
81
|
-
if (
|
|
82
|
-
|
|
92
|
+
// When using taskName without global lookup, listName is required
|
|
93
|
+
if (taskName && !taskId && !customTaskId && !useGlobalLookup && !listName) {
|
|
94
|
+
return {
|
|
95
|
+
isValid: false,
|
|
96
|
+
errorMessage: 'When identifying a task by name, you must also provide the listName parameter'
|
|
97
|
+
};
|
|
83
98
|
}
|
|
99
|
+
return { isValid: true };
|
|
84
100
|
}
|
|
85
101
|
/**
|
|
86
102
|
* Validates list identification parameters
|
|
@@ -99,7 +115,7 @@ export function validateTaskUpdateData(updateData) {
|
|
|
99
115
|
// Check if there are any valid update fields present
|
|
100
116
|
const hasUpdates = Object.keys(updateData).some(key => {
|
|
101
117
|
return ['name', 'description', 'markdown_description', 'status', 'priority',
|
|
102
|
-
'dueDate', 'startDate', '
|
|
118
|
+
'dueDate', 'startDate', 'custom_fields'].includes(key);
|
|
103
119
|
});
|
|
104
120
|
if (!hasUpdates) {
|
|
105
121
|
throw new Error("At least one field to update must be provided");
|
|
@@ -231,3 +247,34 @@ export function extractTreePath(root, targetId) {
|
|
|
231
247
|
// Not found in this branch
|
|
232
248
|
return [];
|
|
233
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Get task ID from various identification methods
|
|
252
|
+
*/
|
|
253
|
+
export async function getTaskId(taskId, taskName, listName, customTaskId, requireId = false) {
|
|
254
|
+
// Validate task identification
|
|
255
|
+
const validationResult = validateTaskIdentification({ taskId, taskName, listName, customTaskId }, { requireTaskId: requireId, useGlobalLookup: true });
|
|
256
|
+
if (!validationResult.isValid) {
|
|
257
|
+
throw new Error(validationResult.errorMessage);
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const result = await taskService.findTasks({
|
|
261
|
+
taskId,
|
|
262
|
+
customTaskId,
|
|
263
|
+
taskName,
|
|
264
|
+
listName,
|
|
265
|
+
allowMultipleMatches: false,
|
|
266
|
+
useSmartDisambiguation: true,
|
|
267
|
+
includeFullDetails: false
|
|
268
|
+
});
|
|
269
|
+
if (!result || Array.isArray(result)) {
|
|
270
|
+
throw new Error(`Task not found with the provided identification`);
|
|
271
|
+
}
|
|
272
|
+
return result.id;
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
if (error.message.includes('Multiple tasks found')) {
|
|
276
|
+
throw new Error(`Multiple tasks found with name "${taskName}". Please provide list name to disambiguate.`);
|
|
277
|
+
}
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -12,20 +12,27 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export const getWorkspaceTasksTool = {
|
|
14
14
|
name: "get_workspace_tasks",
|
|
15
|
-
description: `Purpose: Retrieve tasks from across the entire workspace with filtering.
|
|
15
|
+
description: `Purpose: Retrieve tasks from across the entire workspace with powerful filtering options, including tag-based filtering.
|
|
16
16
|
|
|
17
17
|
Valid Usage:
|
|
18
|
-
1. Apply any combination of filters (tags, lists, statuses, etc.)
|
|
19
|
-
2. Use pagination
|
|
18
|
+
1. Apply any combination of filters (tags, lists, folders, spaces, statuses, etc.)
|
|
19
|
+
2. Use pagination to manage large result sets
|
|
20
20
|
|
|
21
21
|
Requirements:
|
|
22
|
-
- At least one filter parameter REQUIRED (tags, list_ids, statuses,
|
|
22
|
+
- At least one filter parameter is REQUIRED (tags, list_ids, folder_ids, space_ids, statuses, assignees, or date filters)
|
|
23
|
+
- Pagination parameters (page, order_by, reverse) alone are not considered filters
|
|
23
24
|
|
|
24
25
|
Notes:
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
26
|
+
- Provides workspace-wide task access (unlike get_tasks which only searches in one list)
|
|
27
|
+
- Returns complete task details including descriptions, assignees, custom fields, and all metadata
|
|
28
|
+
- Tag filtering is especially useful for cross-list organization (e.g., "project-x", "blocker", "needs-review")
|
|
29
|
+
- Combine multiple filters to narrow down your search scope
|
|
30
|
+
- Use pagination for large result sets
|
|
31
|
+
- Use the detail_level parameter to control the amount of data returned:
|
|
32
|
+
- "summary": Returns lightweight task data (name, status, list, tags)
|
|
33
|
+
- "detailed": Returns complete task data with all fields (DEFAULT if not specified)
|
|
34
|
+
- Responses exceeding 50,000 tokens automatically switch to summary format to avoid hitting LLM token limits
|
|
35
|
+
`,
|
|
29
36
|
parameters: {
|
|
30
37
|
type: 'object',
|
|
31
38
|
properties: {
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* This module provides utilities for handling dates, timestamps, and due date parsing.
|
|
8
8
|
*/
|
|
9
|
+
import { Logger } from '../logger.js';
|
|
10
|
+
// Create a logger instance for date utilities
|
|
11
|
+
const logger = new Logger('DateUtils');
|
|
9
12
|
/**
|
|
10
13
|
* Get a timestamp for a relative time
|
|
11
14
|
*
|
|
@@ -197,8 +200,8 @@ export function parseDueDate(dateString) {
|
|
|
197
200
|
return undefined;
|
|
198
201
|
}
|
|
199
202
|
catch (error) {
|
|
200
|
-
|
|
201
|
-
|
|
203
|
+
logger.warn(`Failed to parse due date: ${dateString}`, error);
|
|
204
|
+
throw new Error(`Invalid date format: ${dateString}`);
|
|
202
205
|
}
|
|
203
206
|
}
|
|
204
207
|
/**
|
|
@@ -225,8 +228,8 @@ export function formatDueDate(timestamp) {
|
|
|
225
228
|
}).replace(' at', ',');
|
|
226
229
|
}
|
|
227
230
|
catch (error) {
|
|
228
|
-
|
|
229
|
-
|
|
231
|
+
logger.warn(`Failed to format due date: ${timestamp}`, error);
|
|
232
|
+
throw new Error(`Invalid timestamp: ${timestamp}`);
|
|
230
233
|
}
|
|
231
234
|
}
|
|
232
235
|
/**
|
|
@@ -9,37 +9,110 @@
|
|
|
9
9
|
import { clickUpServices } from '../services/shared.js';
|
|
10
10
|
import { findListIDByName } from '../tools/list.js';
|
|
11
11
|
/**
|
|
12
|
-
* Check if a
|
|
12
|
+
* Check if a name matches another name using a variety of matching strategies
|
|
13
|
+
* Returns a structured result with match quality information rather than just a boolean
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
* @param actualName The actual name to check
|
|
16
|
+
* @param searchName The name being searched for
|
|
17
|
+
* @returns A structured result with match details
|
|
16
18
|
*/
|
|
17
|
-
export function isNameMatch(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
19
|
+
export function isNameMatch(actualName, searchName) {
|
|
20
|
+
if (!actualName || !searchName) {
|
|
21
|
+
return { isMatch: false, score: 0, exactMatch: false, reason: 'One of the names is empty' };
|
|
22
|
+
}
|
|
23
|
+
// Remove any extra whitespace
|
|
24
|
+
const normalizedActualName = actualName.trim();
|
|
25
|
+
const normalizedSearchName = searchName.trim();
|
|
26
|
+
// Handle empty names after normalization
|
|
27
|
+
if (normalizedActualName === '') {
|
|
28
|
+
return { isMatch: false, score: 0, exactMatch: false, reason: 'Actual name is empty' };
|
|
29
|
+
}
|
|
30
|
+
if (normalizedSearchName === '') {
|
|
31
|
+
return { isMatch: false, score: 0, exactMatch: false, reason: 'Search name is empty' };
|
|
32
|
+
}
|
|
33
|
+
// 1. Exact match (highest quality)
|
|
34
|
+
if (normalizedActualName === normalizedSearchName) {
|
|
35
|
+
return {
|
|
36
|
+
isMatch: true,
|
|
37
|
+
score: 100,
|
|
38
|
+
exactMatch: true,
|
|
39
|
+
reason: 'Exact match'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// 2. Case-insensitive exact match (high quality)
|
|
43
|
+
if (normalizedActualName.toLowerCase() === normalizedSearchName.toLowerCase()) {
|
|
44
|
+
return {
|
|
45
|
+
isMatch: true,
|
|
46
|
+
score: 90,
|
|
47
|
+
exactMatch: true,
|
|
48
|
+
reason: 'Case-insensitive exact match'
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// 3. Match after removing emojis (moderate quality)
|
|
52
|
+
const actualNameWithoutEmoji = normalizedActualName.replace(/[\p{Emoji}\u{FE00}-\u{FE0F}\u200d]+/gu, '').trim();
|
|
53
|
+
const searchNameWithoutEmoji = normalizedSearchName.replace(/[\p{Emoji}\u{FE00}-\u{FE0F}\u200d]+/gu, '').trim();
|
|
54
|
+
if (actualNameWithoutEmoji === searchNameWithoutEmoji) {
|
|
55
|
+
return {
|
|
56
|
+
isMatch: true,
|
|
57
|
+
score: 80,
|
|
58
|
+
exactMatch: false,
|
|
59
|
+
reason: 'Exact match after removing emojis'
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (actualNameWithoutEmoji.toLowerCase() === searchNameWithoutEmoji.toLowerCase()) {
|
|
63
|
+
return {
|
|
64
|
+
isMatch: true,
|
|
65
|
+
score: 70,
|
|
66
|
+
exactMatch: false,
|
|
67
|
+
reason: 'Case-insensitive match after removing emojis'
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// 4. Substring matches (lower quality)
|
|
71
|
+
const lowerActual = normalizedActualName.toLowerCase();
|
|
72
|
+
const lowerSearch = normalizedSearchName.toLowerCase();
|
|
73
|
+
// Full substring (term completely contained)
|
|
74
|
+
if (lowerActual.includes(lowerSearch)) {
|
|
75
|
+
return {
|
|
76
|
+
isMatch: true,
|
|
77
|
+
score: 60,
|
|
78
|
+
exactMatch: false,
|
|
79
|
+
reason: 'Search term found as substring in actual name'
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (lowerSearch.includes(lowerActual)) {
|
|
83
|
+
return {
|
|
84
|
+
isMatch: true,
|
|
85
|
+
score: 50,
|
|
86
|
+
exactMatch: false,
|
|
87
|
+
reason: 'Actual name found as substring in search term'
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// 5. Fuzzy emoji-less matches (lowest quality)
|
|
91
|
+
const lowerActualNoEmoji = actualNameWithoutEmoji.toLowerCase();
|
|
92
|
+
const lowerSearchNoEmoji = searchNameWithoutEmoji.toLowerCase();
|
|
93
|
+
if (lowerActualNoEmoji.includes(lowerSearchNoEmoji)) {
|
|
94
|
+
return {
|
|
95
|
+
isMatch: true,
|
|
96
|
+
score: 40,
|
|
97
|
+
exactMatch: false,
|
|
98
|
+
reason: 'Search term (without emoji) found as substring in actual name'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (lowerSearchNoEmoji.includes(lowerActualNoEmoji)) {
|
|
102
|
+
return {
|
|
103
|
+
isMatch: true,
|
|
104
|
+
score: 30,
|
|
105
|
+
exactMatch: false,
|
|
106
|
+
reason: 'Actual name (without emoji) found as substring in search term'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// No match found
|
|
110
|
+
return {
|
|
111
|
+
isMatch: false,
|
|
112
|
+
score: 0,
|
|
113
|
+
exactMatch: false,
|
|
114
|
+
reason: 'No match found with any matching strategy'
|
|
115
|
+
};
|
|
43
116
|
}
|
|
44
117
|
/**
|
|
45
118
|
* Resolve a list ID from either a direct ID or list name
|
package/package.json
CHANGED