ai-dev-requirements 0.1.7 → 0.1.9

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/index.cjs CHANGED
@@ -123,7 +123,7 @@ const SEARCH_TASKS_QUERY = `
123
123
  key
124
124
  tasks(filterGroup: $filterGroup, orderBy: $orderBy, limit: $limit, includeAncestors: { pathField: "path" }) {
125
125
  key uuid number name
126
- issueType { uuid name }
126
+ issueType { uuid name detailType }
127
127
  status { uuid name category }
128
128
  priority { value }
129
129
  assign { uuid name }
@@ -132,6 +132,15 @@ const SEARCH_TASKS_QUERY = `
132
132
  }
133
133
  }
134
134
  `;
135
+ const ISSUE_TYPES_QUERY = `
136
+ query IssueTypes($orderBy: OrderBy) {
137
+ issueTypes(orderBy: $orderBy) {
138
+ uuid
139
+ name
140
+ detailType
141
+ }
142
+ }
143
+ `;
135
144
  const TASK_BY_NUMBER_QUERY = SEARCH_TASKS_QUERY;
136
145
  const RELATED_TASKS_QUERY = `
137
146
  query Task($key: Key) {
@@ -193,6 +202,117 @@ const DEFAULT_STATUS_NOT_IN = [
193
202
  "Dn3k8ffK",
194
203
  "TbmY2So5"
195
204
  ];
205
+ const TESTCASE_LIBRARY_LIST_QUERY = `
206
+ query Q {
207
+ testcaseLibraries {
208
+ uuid name key
209
+ testcaseCaseCount
210
+ }
211
+ }
212
+ `;
213
+ const TESTCASE_MODULE_SEARCH_QUERY = `
214
+ query Q($filter: Filter) {
215
+ testcaseModules(filter: $filter) {
216
+ uuid name key
217
+ parent { uuid name }
218
+ }
219
+ }
220
+ `;
221
+ const TESTCASE_LIST_PAGED_QUERY = `
222
+ query PAGED_LIBRARY_TESTCASE_LIST($testCaseFilter: Filter, $pagination: Pagination) {
223
+ buckets(groupBy: {testcaseCases: {}}, pagination: $pagination) {
224
+ testcaseCases(filterGroup: $testCaseFilter, limit: 10000) {
225
+ uuid name key id
226
+ priority { uuid value }
227
+ type { uuid value }
228
+ assign { uuid name }
229
+ testcaseModule { uuid }
230
+ }
231
+ key
232
+ pageInfo { count totalCount hasNextPage endCursor }
233
+ }
234
+ }
235
+ `;
236
+ const TESTCASE_DETAIL_QUERY = `
237
+ query QUERY_TESTCASES_DETAIL($testCaseFilter: Filter, $stepFilter: Filter) {
238
+ testcaseCases(filter: $testCaseFilter) {
239
+ uuid name key id condition desc path
240
+ assign { uuid name }
241
+ priority { uuid value }
242
+ type { uuid value }
243
+ testcaseLibrary { uuid }
244
+ testcaseModule { uuid }
245
+ relatedTasks { uuid name number }
246
+ }
247
+ testcaseCaseSteps(filter: $stepFilter, orderBy: { index: ASC }) {
248
+ key uuid
249
+ testcaseCase { uuid }
250
+ desc result index
251
+ }
252
+ }
253
+ `;
254
+ function parseOnesSearchIntent(query) {
255
+ if (!query) return "keyword";
256
+ const normalized = query.toLowerCase();
257
+ if (query.includes("缺陷") || normalized.includes("bug")) return "all_bugs";
258
+ if (query.includes("任务")) return "all_tasks";
259
+ return "keyword";
260
+ }
261
+ function extractAssigneeName(query, intent) {
262
+ if (intent === "keyword") return null;
263
+ const trimmed = query.trim();
264
+ if (!trimmed) return null;
265
+ const ownerStyleMatch = trimmed.match(/\u8D1F\u8D23\u4EBA\u4E3A(.+?)\u7684?(?:\u7F3A\u9677|bug)$/i);
266
+ if (ownerStyleMatch?.[1]) return ownerStyleMatch[1].trim();
267
+ const candidate = trimmed.match(/^(查询)?(.+?)的(?:缺陷|bug|任务)$/i)?.[2]?.trim();
268
+ if (!candidate || candidate.includes("我")) return null;
269
+ return candidate;
270
+ }
271
+ function extractNamedAssignee(query, intent) {
272
+ if (intent === "keyword") return null;
273
+ const compact = query.replace(/\s+/g, "").trim();
274
+ if (!compact) return null;
275
+ const ownerStyleMatch = compact.match(/(?:\u8D1F\u8D23\u4EBA\u4E3A|\u8D1F\u8D23\u4EBA\u662F|\u6307\u6D3E\u7ED9|\u5206\u914D\u7ED9)(.+?)\u7684?(?:\u7F3A\u9677|bug|\u4EFB\u52A1)$/i);
276
+ if (ownerStyleMatch?.[1]) return ownerStyleMatch[1].trim();
277
+ const candidate = compact.match(/^(?:\u67E5\u8BE2|\u67E5\u627E|\u641C\u7D22)?(.+?)\u7684?(?:\u7F3A\u9677|bug|\u4EFB\u52A1)$/i)?.[1]?.trim();
278
+ if (!candidate || candidate.startsWith("我") || /^(?:\u6211|\u6211\u7684|\u6211\u6240\u6709|\u6211\u5168\u90E8|\u672C\u4EBA|\u5F53\u524D\u7528\u6237)$/.test(candidate)) return null;
279
+ return candidate;
280
+ }
281
+ function getBugStatusPriority(task) {
282
+ if (task.status?.category === "to_do") return 0;
283
+ if (task.status?.category === "in_progress") return 1;
284
+ return Number.POSITIVE_INFINITY;
285
+ }
286
+ function isOpenOrInProgressBug(task) {
287
+ const category = task.status?.category;
288
+ return category === "to_do" || category === "in_progress";
289
+ }
290
+ function extractTeamUsers(payload) {
291
+ const record = payload && typeof payload === "object" ? payload : null;
292
+ if (!record) return [];
293
+ const rawUsers = [
294
+ record.users,
295
+ record.items,
296
+ record.list,
297
+ record.results,
298
+ record.data?.users,
299
+ record.data?.items,
300
+ record.data?.list,
301
+ record.data?.results
302
+ ].find(Array.isArray);
303
+ if (!rawUsers) return [];
304
+ return rawUsers.map((item) => {
305
+ const user = item && typeof item === "object" ? item : null;
306
+ if (!user) return null;
307
+ const uuid = user.uuid ?? user.user?.uuid ?? user.orgUser?.uuid ?? user.orgUserUuid ?? user.org_user_uuid ?? user.org_user?.org_user_uuid;
308
+ const name = user.name ?? user.user?.name ?? user.orgUser?.name ?? user.org_user?.name;
309
+ if (!uuid || !name) return null;
310
+ return {
311
+ uuid,
312
+ name
313
+ };
314
+ }).filter((item) => item !== null);
315
+ }
196
316
  function base64Url(buffer) {
197
317
  return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
198
318
  }
@@ -223,6 +343,7 @@ function toRequirement(task, description = "") {
223
343
  }
224
344
  var OnesAdapter = class extends BaseAdapter {
225
345
  session = null;
346
+ issueTypesCache = null;
226
347
  constructor(sourceType, config, resolvedAuth) {
227
348
  super(sourceType, config, resolvedAuth);
228
349
  }
@@ -383,6 +504,42 @@ var OnesAdapter = class extends BaseAdapter {
383
504
  }
384
505
  return response.json();
385
506
  }
507
+ async fetchIssueTypes() {
508
+ if (this.issueTypesCache) return this.issueTypesCache;
509
+ this.issueTypesCache = (await this.graphql(ISSUE_TYPES_QUERY, { orderBy: { namePinyin: "ASC" } }, "issueTypes")).data?.issueTypes ?? [];
510
+ return this.issueTypesCache;
511
+ }
512
+ async searchTeamUsers(keyword) {
513
+ const session = await this.login();
514
+ const url = `${this.config.apiBase}/project/api/project/team/${session.teamUuid}/users/search`;
515
+ const response = await fetch(url, {
516
+ method: "POST",
517
+ headers: {
518
+ "Authorization": `Bearer ${session.accessToken}`,
519
+ "Content-Type": "application/json"
520
+ },
521
+ body: JSON.stringify({
522
+ keyword,
523
+ status: [1],
524
+ team_member_status: [1, 4],
525
+ types: [1, 10]
526
+ })
527
+ });
528
+ if (!response.ok) {
529
+ const text = await response.text().catch(() => "");
530
+ throw new Error(`ONES user search error: ${response.status} ${text}`);
531
+ }
532
+ return extractTeamUsers(await response.json());
533
+ }
534
+ async resolveAssigneeUuid(name) {
535
+ const trimmed = name.trim();
536
+ if (!trimmed) return null;
537
+ const users = await this.searchTeamUsers(trimmed);
538
+ const exactMatch = users.find((user) => user.name === trimmed);
539
+ if (exactMatch) return exactMatch.uuid;
540
+ const normalizedTarget = trimmed.toLowerCase();
541
+ return users.find((user) => user.name.toLowerCase().includes(normalizedTarget))?.uuid ?? null;
542
+ }
386
543
  /**
387
544
  * Fetch task info via REST API (includes description/rich fields not available in GraphQL).
388
545
  * Reference: ones/packages/core/src/tasks.ts → fetchTaskInfo
@@ -547,6 +704,27 @@ var OnesAdapter = class extends BaseAdapter {
547
704
  async searchRequirements(params) {
548
705
  const page = params.page ?? 1;
549
706
  const pageSize = params.pageSize ?? 50;
707
+ const intent = parseOnesSearchIntent(params.query);
708
+ const assigneeName = extractNamedAssignee(params.query, intent) ?? extractAssigneeName(params.query, intent);
709
+ const assigneeUuid = assigneeName ? await this.resolveAssigneeUuid(assigneeName) : null;
710
+ if (assigneeName && !assigneeUuid) return {
711
+ items: [],
712
+ total: 0,
713
+ page,
714
+ pageSize
715
+ };
716
+ let bugTypeUuids = [];
717
+ let taskTypeUuids = [];
718
+ if (intent === "all_bugs" || intent === "all_tasks") {
719
+ const issueTypes = await this.fetchIssueTypes();
720
+ bugTypeUuids = issueTypes.filter((item) => item.detailType === 3).map((item) => item.uuid);
721
+ taskTypeUuids = issueTypes.filter((item) => item.detailType === 2).map((item) => item.uuid);
722
+ }
723
+ const filter = { status_notIn: DEFAULT_STATUS_NOT_IN };
724
+ if (assigneeName) filter.assign_in = [assigneeUuid];
725
+ else filter.assign_in = ["${currentUser}"];
726
+ if (intent === "all_bugs") filter.issueType_in = bugTypeUuids;
727
+ if (intent === "all_tasks") filter.issueType_in = taskTypeUuids;
550
728
  let tasks = (await this.graphql(SEARCH_TASKS_QUERY, {
551
729
  groupBy: { tasks: {} },
552
730
  groupOrderBy: null,
@@ -554,10 +732,7 @@ var OnesAdapter = class extends BaseAdapter {
554
732
  position: "ASC",
555
733
  createTime: "DESC"
556
734
  },
557
- filterGroup: [{
558
- assign_in: ["${currentUser}"],
559
- status_notIn: DEFAULT_STATUS_NOT_IN
560
- }],
735
+ filterGroup: [filter],
561
736
  search: null,
562
737
  pagination: {
563
738
  limit: pageSize * page,
@@ -565,8 +740,11 @@ var OnesAdapter = class extends BaseAdapter {
565
740
  },
566
741
  limit: 1e3
567
742
  }, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? [];
568
- if (params.query) {
569
- const keyword = params.query;
743
+ if (intent === "all_bugs") tasks = tasks.filter((task) => task.issueType?.uuid ? bugTypeUuids.includes(task.issueType.uuid) : false).filter((task) => isOpenOrInProgressBug(task)).sort((a, b) => getBugStatusPriority(a) - getBugStatusPriority(b));
744
+ if (intent === "all_tasks") tasks = tasks.filter((task) => task.issueType?.uuid ? taskTypeUuids.includes(task.issueType.uuid) : false);
745
+ if (assigneeUuid) tasks = tasks.filter((task) => task.assign?.uuid === assigneeUuid);
746
+ if (intent === "keyword" && params.query) {
747
+ const keyword = params.query.trim();
570
748
  const lower = keyword.toLowerCase();
571
749
  const numMatch = keyword.match(/^#?(\d+)$/);
572
750
  if (numMatch) tasks = tasks.filter((t) => t.number === Number.parseInt(numMatch[1], 10));
@@ -654,6 +832,108 @@ var OnesAdapter = class extends BaseAdapter {
654
832
  raw: task
655
833
  };
656
834
  }
835
+ async getTestcases(params) {
836
+ let libraryUuid = params.libraryUuid ?? this.config.options?.testcaseLibraryUuid;
837
+ if (!libraryUuid) {
838
+ const libs = (await this.graphql(TESTCASE_LIBRARY_LIST_QUERY, {}, "library-select")).data?.testcaseLibraries ?? [];
839
+ if (libs.length === 0) throw new Error("ONES: No testcase libraries found for this team");
840
+ libs.sort((a, b) => b.testcaseCaseCount - a.testcaseCaseCount);
841
+ libraryUuid = libs[0].uuid;
842
+ }
843
+ const task = ((await this.graphql(SEARCH_TASKS_QUERY, {
844
+ groupBy: { tasks: {} },
845
+ groupOrderBy: null,
846
+ orderBy: { createTime: "DESC" },
847
+ filterGroup: [{ number_in: [params.taskNumber] }],
848
+ search: null,
849
+ pagination: {
850
+ limit: 10,
851
+ preciseCount: false
852
+ },
853
+ limit: 10
854
+ }, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? []).find((t) => t.number === params.taskNumber);
855
+ if (!task) throw new Error(`ONES: Task #${params.taskNumber} not found`);
856
+ const modules = (await this.graphql(TESTCASE_MODULE_SEARCH_QUERY, { filter: {
857
+ testcaseLibrary_in: [libraryUuid],
858
+ name_match: `#${params.taskNumber}`
859
+ } }, "find-testcase-module")).data?.testcaseModules ?? [];
860
+ if (modules.length === 0) throw new Error(`ONES: No testcase module matching "#${params.taskNumber}" in library ${libraryUuid}`);
861
+ const mod = modules[0];
862
+ const caseList = [];
863
+ let cursor = "";
864
+ let totalCount = 0;
865
+ while (true) {
866
+ const bucket = (await this.graphql(TESTCASE_LIST_PAGED_QUERY, {
867
+ testCaseFilter: [{
868
+ testcaseLibrary_in: [libraryUuid],
869
+ path_match: mod.uuid
870
+ }],
871
+ pagination: {
872
+ limit: 50,
873
+ after: cursor,
874
+ preciseCount: true
875
+ }
876
+ }, "testcase-list-paged")).data?.buckets?.[0];
877
+ if (!bucket) break;
878
+ caseList.push(...bucket.testcaseCases ?? []);
879
+ totalCount = bucket.pageInfo.totalCount;
880
+ if (!bucket.pageInfo.hasNextPage) break;
881
+ cursor = bucket.pageInfo.endCursor;
882
+ }
883
+ if (caseList.length === 0) return {
884
+ taskNumber: params.taskNumber,
885
+ taskName: task.name,
886
+ moduleName: mod.name,
887
+ moduleUuid: mod.uuid,
888
+ totalCount: 0,
889
+ cases: []
890
+ };
891
+ const allCases = [];
892
+ const BATCH_SIZE = 20;
893
+ for (let i = 0; i < caseList.length; i += BATCH_SIZE) {
894
+ const uuids = caseList.slice(i, i + BATCH_SIZE).map((c) => c.uuid);
895
+ const detailData = await this.graphql(TESTCASE_DETAIL_QUERY, {
896
+ testCaseFilter: { uuid_in: [...uuids, null] },
897
+ stepFilter: { testcaseCase_in: uuids }
898
+ }, "library-testcase-detail");
899
+ const cases = detailData.data?.testcaseCases ?? [];
900
+ const steps = detailData.data?.testcaseCaseSteps ?? [];
901
+ const stepsByCase = /* @__PURE__ */ new Map();
902
+ for (const step of steps) {
903
+ const caseUuid = step.testcaseCase.uuid;
904
+ if (!stepsByCase.has(caseUuid)) stepsByCase.set(caseUuid, []);
905
+ stepsByCase.get(caseUuid).push({
906
+ uuid: step.uuid,
907
+ index: step.index,
908
+ desc: step.desc ?? "",
909
+ result: step.result ?? ""
910
+ });
911
+ }
912
+ for (const c of cases) {
913
+ const freshDesc = c.desc ? await this.refreshImageUrls(c.desc) : "";
914
+ allCases.push({
915
+ uuid: c.uuid,
916
+ id: c.id,
917
+ name: c.name,
918
+ priority: c.priority?.value ?? "N/A",
919
+ type: c.type?.value ?? "Unknown",
920
+ assignName: c.assign?.name ?? null,
921
+ condition: c.condition ?? "",
922
+ desc: freshDesc,
923
+ steps: (stepsByCase.get(c.uuid) ?? []).sort((a, b) => a.index - b.index),
924
+ modulePath: c.path ?? ""
925
+ });
926
+ }
927
+ }
928
+ return {
929
+ taskNumber: params.taskNumber,
930
+ taskName: task.name,
931
+ moduleName: mod.name,
932
+ moduleUuid: mod.uuid,
933
+ totalCount,
934
+ cases: allCases
935
+ };
936
+ }
657
937
  };
658
938
 
659
939
  //#endregion
@@ -755,6 +1035,11 @@ function loadConfigFromEnv() {
755
1035
  const account = process.env.ONES_ACCOUNT;
756
1036
  const password = process.env.ONES_PASSWORD;
757
1037
  if (!apiBase || !account || !password) return null;
1038
+ let options;
1039
+ const configPath = findConfigFile(process.cwd());
1040
+ if (configPath) try {
1041
+ options = JSON.parse((0, node_fs.readFileSync)(configPath, "utf-8"))?.sources?.ones?.options;
1042
+ } catch {}
758
1043
  return {
759
1044
  sources: { ones: {
760
1045
  enabled: true,
@@ -763,7 +1048,8 @@ function loadConfigFromEnv() {
763
1048
  type: "ones-pkce",
764
1049
  emailEnv: "ONES_ACCOUNT",
765
1050
  passwordEnv: "ONES_PASSWORD"
766
- }
1051
+ },
1052
+ options
767
1053
  } },
768
1054
  defaultSource: "ones"
769
1055
  };
@@ -976,6 +1262,58 @@ function formatRequirement(req) {
976
1262
  return lines.join("\n");
977
1263
  }
978
1264
 
1265
+ //#endregion
1266
+ //#region src/tools/get-testcases.ts
1267
+ const GetTestcasesSchema = zod_v4.z.object({
1268
+ taskNumber: zod_v4.z.string().describe("Task number (e.g. \"302\" or \"#302\"). Finds all testcases in the matching module."),
1269
+ libraryUuid: zod_v4.z.string().optional().describe("Testcase library UUID. If omitted, uses configured default."),
1270
+ source: zod_v4.z.string().optional().describe("Source to fetch from. If omitted, uses the default source.")
1271
+ });
1272
+ async function handleGetTestcases(input, adapters, defaultSource) {
1273
+ const sourceType = input.source ?? defaultSource;
1274
+ if (!sourceType) throw new Error("No source specified and no default source configured");
1275
+ const adapter = adapters.get(sourceType);
1276
+ if (!adapter) throw new Error(`Source "${sourceType}" is not configured. Available: ${[...adapters.keys()].join(", ")}`);
1277
+ const numMatch = input.taskNumber.match(/^#?(\d+)$/);
1278
+ if (!numMatch) throw new Error(`Invalid task number: "${input.taskNumber}". Expected a number like "302" or "#302".`);
1279
+ return { content: [{
1280
+ type: "text",
1281
+ text: formatTestcases(await adapter.getTestcases({
1282
+ taskNumber: Number.parseInt(numMatch[1], 10),
1283
+ libraryUuid: input.libraryUuid
1284
+ }))
1285
+ }] };
1286
+ }
1287
+ function formatTestcases(result) {
1288
+ const lines = [
1289
+ `# ${result.taskName} — 测试用例`,
1290
+ "",
1291
+ `- **模块**: ${result.moduleName}`,
1292
+ `- **共 ${result.totalCount} 个用例**(已加载 ${result.cases.length} 个)`,
1293
+ ""
1294
+ ];
1295
+ for (const tc of result.cases) {
1296
+ lines.push(`## ${tc.id} ${tc.name}`);
1297
+ lines.push("");
1298
+ lines.push(`- 优先级: ${tc.priority} | 类型: ${tc.type}`);
1299
+ if (tc.assignName) lines.push(`- 维护人: ${tc.assignName}`);
1300
+ if (tc.condition) lines.push(`- 前置条件: ${tc.condition}`);
1301
+ if (tc.desc) lines.push(`- 备注: ${tc.desc}`);
1302
+ if (tc.steps.length > 0) {
1303
+ lines.push("");
1304
+ lines.push("| 步骤 | 操作描述 | 预期结果 |");
1305
+ lines.push("|------|----------|----------|");
1306
+ for (const step of tc.steps) {
1307
+ const desc = step.desc.replace(/\n/g, "<br>");
1308
+ const res = step.result.replace(/\n/g, "<br>");
1309
+ lines.push(`| ${step.index + 1} | ${desc} | ${res} |`);
1310
+ }
1311
+ }
1312
+ lines.push("");
1313
+ }
1314
+ return lines.join("\n");
1315
+ }
1316
+
979
1317
  //#endregion
980
1318
  //#region src/tools/list-sources.ts
981
1319
  async function handleListSources(adapters, config) {
@@ -1010,6 +1348,9 @@ const SearchRequirementsSchema = zod_v4.z.object({
1010
1348
  page: zod_v4.z.number().int().min(1).optional().describe("Page number (default: 1)"),
1011
1349
  pageSize: zod_v4.z.number().int().min(1).max(50).optional().describe("Results per page (default: 20, max: 50)")
1012
1350
  });
1351
+ function formatStatusMarker(status) {
1352
+ return `[${status.toUpperCase()}]`;
1353
+ }
1013
1354
  async function handleSearchRequirements(input, adapters, defaultSource) {
1014
1355
  const sourceType = input.source ?? defaultSource;
1015
1356
  if (!sourceType) throw new Error("No source specified and no default source configured");
@@ -1020,15 +1361,18 @@ async function handleSearchRequirements(input, adapters, defaultSource) {
1020
1361
  page: input.page,
1021
1362
  pageSize: input.pageSize
1022
1363
  });
1023
- const lines = [`Found **${result.total}** results (page ${result.page}/${Math.ceil(result.total / result.pageSize) || 1}):`, ""];
1364
+ const lines = [`Found **${result.total}** items (page ${result.page}/${Math.ceil(result.total / result.pageSize) || 1}):`, ""];
1365
+ if (/\u6211.*\u7F3A\u9677|bug|\u6211.*\u4EFB\u52A1/i.test(input.query)) {
1366
+ lines.push(`Query: ${input.query}`);
1367
+ lines.push("Use an item ID or number in the next step to fetch detail.");
1368
+ lines.push("");
1369
+ }
1024
1370
  for (const item of result.items) {
1025
- lines.push(`### ${item.id}: ${item.title}`);
1371
+ lines.push(`### ${formatStatusMarker(item.status)} ${item.id}: ${item.title}`);
1026
1372
  lines.push(`- Status: ${item.status} | Priority: ${item.priority} | Type: ${item.type}`);
1027
1373
  lines.push(`- Assignee: ${item.assignee ?? "Unassigned"}`);
1028
- if (item.description) {
1029
- const desc = item.description.length > 200 ? `${item.description.slice(0, 200)}...` : item.description;
1030
- lines.push(`- ${desc}`);
1031
- }
1374
+ const desc = item.description ? item.description.length > 200 ? `${item.description.slice(0, 200)}...` : item.description : "(empty)";
1375
+ lines.push(`- Content: ${desc}`);
1032
1376
  lines.push("");
1033
1377
  }
1034
1378
  return { content: [{
@@ -1149,6 +1493,19 @@ async function main() {
1149
1493
  };
1150
1494
  }
1151
1495
  });
1496
+ server.tool("get_testcases", "Get all test cases for a task by its number (e.g. 302). Searches the testcase library for a matching module and returns all cases with steps.", GetTestcasesSchema.shape, async (params) => {
1497
+ try {
1498
+ return await handleGetTestcases(params, adapters, config.config.defaultSource);
1499
+ } catch (err) {
1500
+ return {
1501
+ content: [{
1502
+ type: "text",
1503
+ text: `Error: ${err.message}`
1504
+ }],
1505
+ isError: true
1506
+ };
1507
+ }
1508
+ });
1152
1509
  const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
1153
1510
  await server.connect(transport);
1154
1511
  }