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 +371 -14
- package/dist/index.mjs +371 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -95,7 +95,7 @@ const SEARCH_TASKS_QUERY = `
|
|
|
95
95
|
key
|
|
96
96
|
tasks(filterGroup: $filterGroup, orderBy: $orderBy, limit: $limit, includeAncestors: { pathField: "path" }) {
|
|
97
97
|
key uuid number name
|
|
98
|
-
issueType { uuid name }
|
|
98
|
+
issueType { uuid name detailType }
|
|
99
99
|
status { uuid name category }
|
|
100
100
|
priority { value }
|
|
101
101
|
assign { uuid name }
|
|
@@ -104,6 +104,15 @@ const SEARCH_TASKS_QUERY = `
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
`;
|
|
107
|
+
const ISSUE_TYPES_QUERY = `
|
|
108
|
+
query IssueTypes($orderBy: OrderBy) {
|
|
109
|
+
issueTypes(orderBy: $orderBy) {
|
|
110
|
+
uuid
|
|
111
|
+
name
|
|
112
|
+
detailType
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
107
116
|
const TASK_BY_NUMBER_QUERY = SEARCH_TASKS_QUERY;
|
|
108
117
|
const RELATED_TASKS_QUERY = `
|
|
109
118
|
query Task($key: Key) {
|
|
@@ -165,6 +174,117 @@ const DEFAULT_STATUS_NOT_IN = [
|
|
|
165
174
|
"Dn3k8ffK",
|
|
166
175
|
"TbmY2So5"
|
|
167
176
|
];
|
|
177
|
+
const TESTCASE_LIBRARY_LIST_QUERY = `
|
|
178
|
+
query Q {
|
|
179
|
+
testcaseLibraries {
|
|
180
|
+
uuid name key
|
|
181
|
+
testcaseCaseCount
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
`;
|
|
185
|
+
const TESTCASE_MODULE_SEARCH_QUERY = `
|
|
186
|
+
query Q($filter: Filter) {
|
|
187
|
+
testcaseModules(filter: $filter) {
|
|
188
|
+
uuid name key
|
|
189
|
+
parent { uuid name }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
const TESTCASE_LIST_PAGED_QUERY = `
|
|
194
|
+
query PAGED_LIBRARY_TESTCASE_LIST($testCaseFilter: Filter, $pagination: Pagination) {
|
|
195
|
+
buckets(groupBy: {testcaseCases: {}}, pagination: $pagination) {
|
|
196
|
+
testcaseCases(filterGroup: $testCaseFilter, limit: 10000) {
|
|
197
|
+
uuid name key id
|
|
198
|
+
priority { uuid value }
|
|
199
|
+
type { uuid value }
|
|
200
|
+
assign { uuid name }
|
|
201
|
+
testcaseModule { uuid }
|
|
202
|
+
}
|
|
203
|
+
key
|
|
204
|
+
pageInfo { count totalCount hasNextPage endCursor }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
`;
|
|
208
|
+
const TESTCASE_DETAIL_QUERY = `
|
|
209
|
+
query QUERY_TESTCASES_DETAIL($testCaseFilter: Filter, $stepFilter: Filter) {
|
|
210
|
+
testcaseCases(filter: $testCaseFilter) {
|
|
211
|
+
uuid name key id condition desc path
|
|
212
|
+
assign { uuid name }
|
|
213
|
+
priority { uuid value }
|
|
214
|
+
type { uuid value }
|
|
215
|
+
testcaseLibrary { uuid }
|
|
216
|
+
testcaseModule { uuid }
|
|
217
|
+
relatedTasks { uuid name number }
|
|
218
|
+
}
|
|
219
|
+
testcaseCaseSteps(filter: $stepFilter, orderBy: { index: ASC }) {
|
|
220
|
+
key uuid
|
|
221
|
+
testcaseCase { uuid }
|
|
222
|
+
desc result index
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
`;
|
|
226
|
+
function parseOnesSearchIntent(query) {
|
|
227
|
+
if (!query) return "keyword";
|
|
228
|
+
const normalized = query.toLowerCase();
|
|
229
|
+
if (query.includes("缺陷") || normalized.includes("bug")) return "all_bugs";
|
|
230
|
+
if (query.includes("任务")) return "all_tasks";
|
|
231
|
+
return "keyword";
|
|
232
|
+
}
|
|
233
|
+
function extractAssigneeName(query, intent) {
|
|
234
|
+
if (intent === "keyword") return null;
|
|
235
|
+
const trimmed = query.trim();
|
|
236
|
+
if (!trimmed) return null;
|
|
237
|
+
const ownerStyleMatch = trimmed.match(/\u8D1F\u8D23\u4EBA\u4E3A(.+?)\u7684?(?:\u7F3A\u9677|bug)$/i);
|
|
238
|
+
if (ownerStyleMatch?.[1]) return ownerStyleMatch[1].trim();
|
|
239
|
+
const candidate = trimmed.match(/^(查询)?(.+?)的(?:缺陷|bug|任务)$/i)?.[2]?.trim();
|
|
240
|
+
if (!candidate || candidate.includes("我")) return null;
|
|
241
|
+
return candidate;
|
|
242
|
+
}
|
|
243
|
+
function extractNamedAssignee(query, intent) {
|
|
244
|
+
if (intent === "keyword") return null;
|
|
245
|
+
const compact = query.replace(/\s+/g, "").trim();
|
|
246
|
+
if (!compact) return null;
|
|
247
|
+
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);
|
|
248
|
+
if (ownerStyleMatch?.[1]) return ownerStyleMatch[1].trim();
|
|
249
|
+
const candidate = compact.match(/^(?:\u67E5\u8BE2|\u67E5\u627E|\u641C\u7D22)?(.+?)\u7684?(?:\u7F3A\u9677|bug|\u4EFB\u52A1)$/i)?.[1]?.trim();
|
|
250
|
+
if (!candidate || candidate.startsWith("我") || /^(?:\u6211|\u6211\u7684|\u6211\u6240\u6709|\u6211\u5168\u90E8|\u672C\u4EBA|\u5F53\u524D\u7528\u6237)$/.test(candidate)) return null;
|
|
251
|
+
return candidate;
|
|
252
|
+
}
|
|
253
|
+
function getBugStatusPriority(task) {
|
|
254
|
+
if (task.status?.category === "to_do") return 0;
|
|
255
|
+
if (task.status?.category === "in_progress") return 1;
|
|
256
|
+
return Number.POSITIVE_INFINITY;
|
|
257
|
+
}
|
|
258
|
+
function isOpenOrInProgressBug(task) {
|
|
259
|
+
const category = task.status?.category;
|
|
260
|
+
return category === "to_do" || category === "in_progress";
|
|
261
|
+
}
|
|
262
|
+
function extractTeamUsers(payload) {
|
|
263
|
+
const record = payload && typeof payload === "object" ? payload : null;
|
|
264
|
+
if (!record) return [];
|
|
265
|
+
const rawUsers = [
|
|
266
|
+
record.users,
|
|
267
|
+
record.items,
|
|
268
|
+
record.list,
|
|
269
|
+
record.results,
|
|
270
|
+
record.data?.users,
|
|
271
|
+
record.data?.items,
|
|
272
|
+
record.data?.list,
|
|
273
|
+
record.data?.results
|
|
274
|
+
].find(Array.isArray);
|
|
275
|
+
if (!rawUsers) return [];
|
|
276
|
+
return rawUsers.map((item) => {
|
|
277
|
+
const user = item && typeof item === "object" ? item : null;
|
|
278
|
+
if (!user) return null;
|
|
279
|
+
const uuid = user.uuid ?? user.user?.uuid ?? user.orgUser?.uuid ?? user.orgUserUuid ?? user.org_user_uuid ?? user.org_user?.org_user_uuid;
|
|
280
|
+
const name = user.name ?? user.user?.name ?? user.orgUser?.name ?? user.org_user?.name;
|
|
281
|
+
if (!uuid || !name) return null;
|
|
282
|
+
return {
|
|
283
|
+
uuid,
|
|
284
|
+
name
|
|
285
|
+
};
|
|
286
|
+
}).filter((item) => item !== null);
|
|
287
|
+
}
|
|
168
288
|
function base64Url(buffer) {
|
|
169
289
|
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
170
290
|
}
|
|
@@ -195,6 +315,7 @@ function toRequirement(task, description = "") {
|
|
|
195
315
|
}
|
|
196
316
|
var OnesAdapter = class extends BaseAdapter {
|
|
197
317
|
session = null;
|
|
318
|
+
issueTypesCache = null;
|
|
198
319
|
constructor(sourceType, config, resolvedAuth) {
|
|
199
320
|
super(sourceType, config, resolvedAuth);
|
|
200
321
|
}
|
|
@@ -355,6 +476,42 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
355
476
|
}
|
|
356
477
|
return response.json();
|
|
357
478
|
}
|
|
479
|
+
async fetchIssueTypes() {
|
|
480
|
+
if (this.issueTypesCache) return this.issueTypesCache;
|
|
481
|
+
this.issueTypesCache = (await this.graphql(ISSUE_TYPES_QUERY, { orderBy: { namePinyin: "ASC" } }, "issueTypes")).data?.issueTypes ?? [];
|
|
482
|
+
return this.issueTypesCache;
|
|
483
|
+
}
|
|
484
|
+
async searchTeamUsers(keyword) {
|
|
485
|
+
const session = await this.login();
|
|
486
|
+
const url = `${this.config.apiBase}/project/api/project/team/${session.teamUuid}/users/search`;
|
|
487
|
+
const response = await fetch(url, {
|
|
488
|
+
method: "POST",
|
|
489
|
+
headers: {
|
|
490
|
+
"Authorization": `Bearer ${session.accessToken}`,
|
|
491
|
+
"Content-Type": "application/json"
|
|
492
|
+
},
|
|
493
|
+
body: JSON.stringify({
|
|
494
|
+
keyword,
|
|
495
|
+
status: [1],
|
|
496
|
+
team_member_status: [1, 4],
|
|
497
|
+
types: [1, 10]
|
|
498
|
+
})
|
|
499
|
+
});
|
|
500
|
+
if (!response.ok) {
|
|
501
|
+
const text = await response.text().catch(() => "");
|
|
502
|
+
throw new Error(`ONES user search error: ${response.status} ${text}`);
|
|
503
|
+
}
|
|
504
|
+
return extractTeamUsers(await response.json());
|
|
505
|
+
}
|
|
506
|
+
async resolveAssigneeUuid(name) {
|
|
507
|
+
const trimmed = name.trim();
|
|
508
|
+
if (!trimmed) return null;
|
|
509
|
+
const users = await this.searchTeamUsers(trimmed);
|
|
510
|
+
const exactMatch = users.find((user) => user.name === trimmed);
|
|
511
|
+
if (exactMatch) return exactMatch.uuid;
|
|
512
|
+
const normalizedTarget = trimmed.toLowerCase();
|
|
513
|
+
return users.find((user) => user.name.toLowerCase().includes(normalizedTarget))?.uuid ?? null;
|
|
514
|
+
}
|
|
358
515
|
/**
|
|
359
516
|
* Fetch task info via REST API (includes description/rich fields not available in GraphQL).
|
|
360
517
|
* Reference: ones/packages/core/src/tasks.ts → fetchTaskInfo
|
|
@@ -519,6 +676,27 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
519
676
|
async searchRequirements(params) {
|
|
520
677
|
const page = params.page ?? 1;
|
|
521
678
|
const pageSize = params.pageSize ?? 50;
|
|
679
|
+
const intent = parseOnesSearchIntent(params.query);
|
|
680
|
+
const assigneeName = extractNamedAssignee(params.query, intent) ?? extractAssigneeName(params.query, intent);
|
|
681
|
+
const assigneeUuid = assigneeName ? await this.resolveAssigneeUuid(assigneeName) : null;
|
|
682
|
+
if (assigneeName && !assigneeUuid) return {
|
|
683
|
+
items: [],
|
|
684
|
+
total: 0,
|
|
685
|
+
page,
|
|
686
|
+
pageSize
|
|
687
|
+
};
|
|
688
|
+
let bugTypeUuids = [];
|
|
689
|
+
let taskTypeUuids = [];
|
|
690
|
+
if (intent === "all_bugs" || intent === "all_tasks") {
|
|
691
|
+
const issueTypes = await this.fetchIssueTypes();
|
|
692
|
+
bugTypeUuids = issueTypes.filter((item) => item.detailType === 3).map((item) => item.uuid);
|
|
693
|
+
taskTypeUuids = issueTypes.filter((item) => item.detailType === 2).map((item) => item.uuid);
|
|
694
|
+
}
|
|
695
|
+
const filter = { status_notIn: DEFAULT_STATUS_NOT_IN };
|
|
696
|
+
if (assigneeName) filter.assign_in = [assigneeUuid];
|
|
697
|
+
else filter.assign_in = ["${currentUser}"];
|
|
698
|
+
if (intent === "all_bugs") filter.issueType_in = bugTypeUuids;
|
|
699
|
+
if (intent === "all_tasks") filter.issueType_in = taskTypeUuids;
|
|
522
700
|
let tasks = (await this.graphql(SEARCH_TASKS_QUERY, {
|
|
523
701
|
groupBy: { tasks: {} },
|
|
524
702
|
groupOrderBy: null,
|
|
@@ -526,10 +704,7 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
526
704
|
position: "ASC",
|
|
527
705
|
createTime: "DESC"
|
|
528
706
|
},
|
|
529
|
-
filterGroup: [
|
|
530
|
-
assign_in: ["${currentUser}"],
|
|
531
|
-
status_notIn: DEFAULT_STATUS_NOT_IN
|
|
532
|
-
}],
|
|
707
|
+
filterGroup: [filter],
|
|
533
708
|
search: null,
|
|
534
709
|
pagination: {
|
|
535
710
|
limit: pageSize * page,
|
|
@@ -537,8 +712,11 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
537
712
|
},
|
|
538
713
|
limit: 1e3
|
|
539
714
|
}, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? [];
|
|
540
|
-
if (
|
|
541
|
-
|
|
715
|
+
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));
|
|
716
|
+
if (intent === "all_tasks") tasks = tasks.filter((task) => task.issueType?.uuid ? taskTypeUuids.includes(task.issueType.uuid) : false);
|
|
717
|
+
if (assigneeUuid) tasks = tasks.filter((task) => task.assign?.uuid === assigneeUuid);
|
|
718
|
+
if (intent === "keyword" && params.query) {
|
|
719
|
+
const keyword = params.query.trim();
|
|
542
720
|
const lower = keyword.toLowerCase();
|
|
543
721
|
const numMatch = keyword.match(/^#?(\d+)$/);
|
|
544
722
|
if (numMatch) tasks = tasks.filter((t) => t.number === Number.parseInt(numMatch[1], 10));
|
|
@@ -626,6 +804,108 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
626
804
|
raw: task
|
|
627
805
|
};
|
|
628
806
|
}
|
|
807
|
+
async getTestcases(params) {
|
|
808
|
+
let libraryUuid = params.libraryUuid ?? this.config.options?.testcaseLibraryUuid;
|
|
809
|
+
if (!libraryUuid) {
|
|
810
|
+
const libs = (await this.graphql(TESTCASE_LIBRARY_LIST_QUERY, {}, "library-select")).data?.testcaseLibraries ?? [];
|
|
811
|
+
if (libs.length === 0) throw new Error("ONES: No testcase libraries found for this team");
|
|
812
|
+
libs.sort((a, b) => b.testcaseCaseCount - a.testcaseCaseCount);
|
|
813
|
+
libraryUuid = libs[0].uuid;
|
|
814
|
+
}
|
|
815
|
+
const task = ((await this.graphql(SEARCH_TASKS_QUERY, {
|
|
816
|
+
groupBy: { tasks: {} },
|
|
817
|
+
groupOrderBy: null,
|
|
818
|
+
orderBy: { createTime: "DESC" },
|
|
819
|
+
filterGroup: [{ number_in: [params.taskNumber] }],
|
|
820
|
+
search: null,
|
|
821
|
+
pagination: {
|
|
822
|
+
limit: 10,
|
|
823
|
+
preciseCount: false
|
|
824
|
+
},
|
|
825
|
+
limit: 10
|
|
826
|
+
}, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? []).find((t) => t.number === params.taskNumber);
|
|
827
|
+
if (!task) throw new Error(`ONES: Task #${params.taskNumber} not found`);
|
|
828
|
+
const modules = (await this.graphql(TESTCASE_MODULE_SEARCH_QUERY, { filter: {
|
|
829
|
+
testcaseLibrary_in: [libraryUuid],
|
|
830
|
+
name_match: `#${params.taskNumber}`
|
|
831
|
+
} }, "find-testcase-module")).data?.testcaseModules ?? [];
|
|
832
|
+
if (modules.length === 0) throw new Error(`ONES: No testcase module matching "#${params.taskNumber}" in library ${libraryUuid}`);
|
|
833
|
+
const mod = modules[0];
|
|
834
|
+
const caseList = [];
|
|
835
|
+
let cursor = "";
|
|
836
|
+
let totalCount = 0;
|
|
837
|
+
while (true) {
|
|
838
|
+
const bucket = (await this.graphql(TESTCASE_LIST_PAGED_QUERY, {
|
|
839
|
+
testCaseFilter: [{
|
|
840
|
+
testcaseLibrary_in: [libraryUuid],
|
|
841
|
+
path_match: mod.uuid
|
|
842
|
+
}],
|
|
843
|
+
pagination: {
|
|
844
|
+
limit: 50,
|
|
845
|
+
after: cursor,
|
|
846
|
+
preciseCount: true
|
|
847
|
+
}
|
|
848
|
+
}, "testcase-list-paged")).data?.buckets?.[0];
|
|
849
|
+
if (!bucket) break;
|
|
850
|
+
caseList.push(...bucket.testcaseCases ?? []);
|
|
851
|
+
totalCount = bucket.pageInfo.totalCount;
|
|
852
|
+
if (!bucket.pageInfo.hasNextPage) break;
|
|
853
|
+
cursor = bucket.pageInfo.endCursor;
|
|
854
|
+
}
|
|
855
|
+
if (caseList.length === 0) return {
|
|
856
|
+
taskNumber: params.taskNumber,
|
|
857
|
+
taskName: task.name,
|
|
858
|
+
moduleName: mod.name,
|
|
859
|
+
moduleUuid: mod.uuid,
|
|
860
|
+
totalCount: 0,
|
|
861
|
+
cases: []
|
|
862
|
+
};
|
|
863
|
+
const allCases = [];
|
|
864
|
+
const BATCH_SIZE = 20;
|
|
865
|
+
for (let i = 0; i < caseList.length; i += BATCH_SIZE) {
|
|
866
|
+
const uuids = caseList.slice(i, i + BATCH_SIZE).map((c) => c.uuid);
|
|
867
|
+
const detailData = await this.graphql(TESTCASE_DETAIL_QUERY, {
|
|
868
|
+
testCaseFilter: { uuid_in: [...uuids, null] },
|
|
869
|
+
stepFilter: { testcaseCase_in: uuids }
|
|
870
|
+
}, "library-testcase-detail");
|
|
871
|
+
const cases = detailData.data?.testcaseCases ?? [];
|
|
872
|
+
const steps = detailData.data?.testcaseCaseSteps ?? [];
|
|
873
|
+
const stepsByCase = /* @__PURE__ */ new Map();
|
|
874
|
+
for (const step of steps) {
|
|
875
|
+
const caseUuid = step.testcaseCase.uuid;
|
|
876
|
+
if (!stepsByCase.has(caseUuid)) stepsByCase.set(caseUuid, []);
|
|
877
|
+
stepsByCase.get(caseUuid).push({
|
|
878
|
+
uuid: step.uuid,
|
|
879
|
+
index: step.index,
|
|
880
|
+
desc: step.desc ?? "",
|
|
881
|
+
result: step.result ?? ""
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
for (const c of cases) {
|
|
885
|
+
const freshDesc = c.desc ? await this.refreshImageUrls(c.desc) : "";
|
|
886
|
+
allCases.push({
|
|
887
|
+
uuid: c.uuid,
|
|
888
|
+
id: c.id,
|
|
889
|
+
name: c.name,
|
|
890
|
+
priority: c.priority?.value ?? "N/A",
|
|
891
|
+
type: c.type?.value ?? "Unknown",
|
|
892
|
+
assignName: c.assign?.name ?? null,
|
|
893
|
+
condition: c.condition ?? "",
|
|
894
|
+
desc: freshDesc,
|
|
895
|
+
steps: (stepsByCase.get(c.uuid) ?? []).sort((a, b) => a.index - b.index),
|
|
896
|
+
modulePath: c.path ?? ""
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return {
|
|
901
|
+
taskNumber: params.taskNumber,
|
|
902
|
+
taskName: task.name,
|
|
903
|
+
moduleName: mod.name,
|
|
904
|
+
moduleUuid: mod.uuid,
|
|
905
|
+
totalCount,
|
|
906
|
+
cases: allCases
|
|
907
|
+
};
|
|
908
|
+
}
|
|
629
909
|
};
|
|
630
910
|
|
|
631
911
|
//#endregion
|
|
@@ -727,6 +1007,11 @@ function loadConfigFromEnv() {
|
|
|
727
1007
|
const account = process.env.ONES_ACCOUNT;
|
|
728
1008
|
const password = process.env.ONES_PASSWORD;
|
|
729
1009
|
if (!apiBase || !account || !password) return null;
|
|
1010
|
+
let options;
|
|
1011
|
+
const configPath = findConfigFile(process.cwd());
|
|
1012
|
+
if (configPath) try {
|
|
1013
|
+
options = JSON.parse(readFileSync(configPath, "utf-8"))?.sources?.ones?.options;
|
|
1014
|
+
} catch {}
|
|
730
1015
|
return {
|
|
731
1016
|
sources: { ones: {
|
|
732
1017
|
enabled: true,
|
|
@@ -735,7 +1020,8 @@ function loadConfigFromEnv() {
|
|
|
735
1020
|
type: "ones-pkce",
|
|
736
1021
|
emailEnv: "ONES_ACCOUNT",
|
|
737
1022
|
passwordEnv: "ONES_PASSWORD"
|
|
738
|
-
}
|
|
1023
|
+
},
|
|
1024
|
+
options
|
|
739
1025
|
} },
|
|
740
1026
|
defaultSource: "ones"
|
|
741
1027
|
};
|
|
@@ -948,6 +1234,58 @@ function formatRequirement(req) {
|
|
|
948
1234
|
return lines.join("\n");
|
|
949
1235
|
}
|
|
950
1236
|
|
|
1237
|
+
//#endregion
|
|
1238
|
+
//#region src/tools/get-testcases.ts
|
|
1239
|
+
const GetTestcasesSchema = z.object({
|
|
1240
|
+
taskNumber: z.string().describe("Task number (e.g. \"302\" or \"#302\"). Finds all testcases in the matching module."),
|
|
1241
|
+
libraryUuid: z.string().optional().describe("Testcase library UUID. If omitted, uses configured default."),
|
|
1242
|
+
source: z.string().optional().describe("Source to fetch from. If omitted, uses the default source.")
|
|
1243
|
+
});
|
|
1244
|
+
async function handleGetTestcases(input, adapters, defaultSource) {
|
|
1245
|
+
const sourceType = input.source ?? defaultSource;
|
|
1246
|
+
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
1247
|
+
const adapter = adapters.get(sourceType);
|
|
1248
|
+
if (!adapter) throw new Error(`Source "${sourceType}" is not configured. Available: ${[...adapters.keys()].join(", ")}`);
|
|
1249
|
+
const numMatch = input.taskNumber.match(/^#?(\d+)$/);
|
|
1250
|
+
if (!numMatch) throw new Error(`Invalid task number: "${input.taskNumber}". Expected a number like "302" or "#302".`);
|
|
1251
|
+
return { content: [{
|
|
1252
|
+
type: "text",
|
|
1253
|
+
text: formatTestcases(await adapter.getTestcases({
|
|
1254
|
+
taskNumber: Number.parseInt(numMatch[1], 10),
|
|
1255
|
+
libraryUuid: input.libraryUuid
|
|
1256
|
+
}))
|
|
1257
|
+
}] };
|
|
1258
|
+
}
|
|
1259
|
+
function formatTestcases(result) {
|
|
1260
|
+
const lines = [
|
|
1261
|
+
`# ${result.taskName} — 测试用例`,
|
|
1262
|
+
"",
|
|
1263
|
+
`- **模块**: ${result.moduleName}`,
|
|
1264
|
+
`- **共 ${result.totalCount} 个用例**(已加载 ${result.cases.length} 个)`,
|
|
1265
|
+
""
|
|
1266
|
+
];
|
|
1267
|
+
for (const tc of result.cases) {
|
|
1268
|
+
lines.push(`## ${tc.id} ${tc.name}`);
|
|
1269
|
+
lines.push("");
|
|
1270
|
+
lines.push(`- 优先级: ${tc.priority} | 类型: ${tc.type}`);
|
|
1271
|
+
if (tc.assignName) lines.push(`- 维护人: ${tc.assignName}`);
|
|
1272
|
+
if (tc.condition) lines.push(`- 前置条件: ${tc.condition}`);
|
|
1273
|
+
if (tc.desc) lines.push(`- 备注: ${tc.desc}`);
|
|
1274
|
+
if (tc.steps.length > 0) {
|
|
1275
|
+
lines.push("");
|
|
1276
|
+
lines.push("| 步骤 | 操作描述 | 预期结果 |");
|
|
1277
|
+
lines.push("|------|----------|----------|");
|
|
1278
|
+
for (const step of tc.steps) {
|
|
1279
|
+
const desc = step.desc.replace(/\n/g, "<br>");
|
|
1280
|
+
const res = step.result.replace(/\n/g, "<br>");
|
|
1281
|
+
lines.push(`| ${step.index + 1} | ${desc} | ${res} |`);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
lines.push("");
|
|
1285
|
+
}
|
|
1286
|
+
return lines.join("\n");
|
|
1287
|
+
}
|
|
1288
|
+
|
|
951
1289
|
//#endregion
|
|
952
1290
|
//#region src/tools/list-sources.ts
|
|
953
1291
|
async function handleListSources(adapters, config) {
|
|
@@ -982,6 +1320,9 @@ const SearchRequirementsSchema = z.object({
|
|
|
982
1320
|
page: z.number().int().min(1).optional().describe("Page number (default: 1)"),
|
|
983
1321
|
pageSize: z.number().int().min(1).max(50).optional().describe("Results per page (default: 20, max: 50)")
|
|
984
1322
|
});
|
|
1323
|
+
function formatStatusMarker(status) {
|
|
1324
|
+
return `[${status.toUpperCase()}]`;
|
|
1325
|
+
}
|
|
985
1326
|
async function handleSearchRequirements(input, adapters, defaultSource) {
|
|
986
1327
|
const sourceType = input.source ?? defaultSource;
|
|
987
1328
|
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
@@ -992,15 +1333,18 @@ async function handleSearchRequirements(input, adapters, defaultSource) {
|
|
|
992
1333
|
page: input.page,
|
|
993
1334
|
pageSize: input.pageSize
|
|
994
1335
|
});
|
|
995
|
-
const lines = [`Found **${result.total}**
|
|
1336
|
+
const lines = [`Found **${result.total}** items (page ${result.page}/${Math.ceil(result.total / result.pageSize) || 1}):`, ""];
|
|
1337
|
+
if (/\u6211.*\u7F3A\u9677|bug|\u6211.*\u4EFB\u52A1/i.test(input.query)) {
|
|
1338
|
+
lines.push(`Query: ${input.query}`);
|
|
1339
|
+
lines.push("Use an item ID or number in the next step to fetch detail.");
|
|
1340
|
+
lines.push("");
|
|
1341
|
+
}
|
|
996
1342
|
for (const item of result.items) {
|
|
997
|
-
lines.push(`### ${item.id}: ${item.title}`);
|
|
1343
|
+
lines.push(`### ${formatStatusMarker(item.status)} ${item.id}: ${item.title}`);
|
|
998
1344
|
lines.push(`- Status: ${item.status} | Priority: ${item.priority} | Type: ${item.type}`);
|
|
999
1345
|
lines.push(`- Assignee: ${item.assignee ?? "Unassigned"}`);
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
lines.push(`- ${desc}`);
|
|
1003
|
-
}
|
|
1346
|
+
const desc = item.description ? item.description.length > 200 ? `${item.description.slice(0, 200)}...` : item.description : "(empty)";
|
|
1347
|
+
lines.push(`- Content: ${desc}`);
|
|
1004
1348
|
lines.push("");
|
|
1005
1349
|
}
|
|
1006
1350
|
return { content: [{
|
|
@@ -1121,6 +1465,19 @@ async function main() {
|
|
|
1121
1465
|
};
|
|
1122
1466
|
}
|
|
1123
1467
|
});
|
|
1468
|
+
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) => {
|
|
1469
|
+
try {
|
|
1470
|
+
return await handleGetTestcases(params, adapters, config.config.defaultSource);
|
|
1471
|
+
} catch (err) {
|
|
1472
|
+
return {
|
|
1473
|
+
content: [{
|
|
1474
|
+
type: "text",
|
|
1475
|
+
text: `Error: ${err.message}`
|
|
1476
|
+
}],
|
|
1477
|
+
isError: true
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1124
1481
|
const transport = new StdioServerTransport();
|
|
1125
1482
|
await server.connect(transport);
|
|
1126
1483
|
}
|