ai-dev-requirements 0.1.11 → 0.1.12
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 +374 -46
- package/dist/index.mjs +374 -46
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -144,6 +144,26 @@ const ISSUE_TYPES_QUERY = `
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
`;
|
|
147
|
+
const PROJECTS_QUERY = `
|
|
148
|
+
query Projects($groupBy: GroupBy, $orderBy: OrderBy, $pagination: Pagination, $projectOrderBy: OrderBy, $projectFilterGroup: [Filter!]) {
|
|
149
|
+
buckets(groupBy: $groupBy, orderBy: $orderBy, pagination: $pagination) {
|
|
150
|
+
key
|
|
151
|
+
projects(limit: 10000, orderBy: $projectOrderBy, filterGroup: $projectFilterGroup) {
|
|
152
|
+
key
|
|
153
|
+
uuid
|
|
154
|
+
name
|
|
155
|
+
identifier
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
160
|
+
const ADD_MANHOUR_MUTATION = `
|
|
161
|
+
mutation AddManhour {
|
|
162
|
+
addManhour(mode: $mode, owner: $owner, task: $task, type: $type, start_time: $start_time, hours: $hours, description: $description, customData: $customData) {
|
|
163
|
+
key
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
147
167
|
const TASK_BY_NUMBER_QUERY = SEARCH_TASKS_QUERY;
|
|
148
168
|
const RELATED_TASKS_QUERY = `
|
|
149
169
|
query Task($key: Key) {
|
|
@@ -350,6 +370,74 @@ function parseOnesWikiPageRoute(input) {
|
|
|
350
370
|
function isOnesWikiUrlInput(input) {
|
|
351
371
|
return input.includes("/wiki/");
|
|
352
372
|
}
|
|
373
|
+
function parseAuthorizeRequestId(location) {
|
|
374
|
+
try {
|
|
375
|
+
const parsed = new URL(location);
|
|
376
|
+
return parsed.searchParams.get("auth_request_id") ?? parsed.searchParams.get("id");
|
|
377
|
+
} catch {
|
|
378
|
+
const match = location.match(/[?&](?:auth_request_id|id)=([^&#]+)/);
|
|
379
|
+
return match?.[1] ? decodeURIComponent(match[1]) : null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function parseAuthorizationCode(location) {
|
|
383
|
+
try {
|
|
384
|
+
return new URL(location).searchParams.get("code");
|
|
385
|
+
} catch {
|
|
386
|
+
const match = location.match(/[?&]code=([^&#]+)/);
|
|
387
|
+
return match?.[1] ? decodeURIComponent(match[1]) : null;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function parseDisplayId(input) {
|
|
391
|
+
const match = input.trim().match(/^([a-z]\w*)-(\d+)$/i);
|
|
392
|
+
if (!match?.[1] || !match[2]) return null;
|
|
393
|
+
return {
|
|
394
|
+
identifier: match[1],
|
|
395
|
+
number: Number.parseInt(match[2], 10)
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function isValidOnesDate(value) {
|
|
399
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
400
|
+
const [yearText, monthText, dayText] = value.split("-");
|
|
401
|
+
const year = Number.parseInt(yearText, 10);
|
|
402
|
+
const month = Number.parseInt(monthText, 10);
|
|
403
|
+
const day = Number.parseInt(dayText, 10);
|
|
404
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
405
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
406
|
+
}
|
|
407
|
+
function toOnesHours(hours) {
|
|
408
|
+
if (!Number.isFinite(hours) || hours <= 0) throw new Error("ONES: hours must be a positive number");
|
|
409
|
+
return Math.round(hours * 1e5);
|
|
410
|
+
}
|
|
411
|
+
function getTodayStartUnixSeconds() {
|
|
412
|
+
const now = /* @__PURE__ */ new Date();
|
|
413
|
+
const localStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
414
|
+
return Math.floor(localStart.getTime() / 1e3);
|
|
415
|
+
}
|
|
416
|
+
function toLocalDateString(date) {
|
|
417
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
418
|
+
}
|
|
419
|
+
function getLocalStartUnixSeconds(year, month, day) {
|
|
420
|
+
return Math.floor(new Date(year, month - 1, day).getTime() / 1e3);
|
|
421
|
+
}
|
|
422
|
+
function parseManhourDate(input) {
|
|
423
|
+
const value = input?.trim();
|
|
424
|
+
if (!value) return {
|
|
425
|
+
date: null,
|
|
426
|
+
startTime: getTodayStartUnixSeconds()
|
|
427
|
+
};
|
|
428
|
+
const fullDateMatch = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
429
|
+
const dayOnlyMatch = value.match(/^(\d{1,2})号?$/);
|
|
430
|
+
const now = /* @__PURE__ */ new Date();
|
|
431
|
+
const year = fullDateMatch ? Number.parseInt(fullDateMatch[1], 10) : now.getFullYear();
|
|
432
|
+
const month = fullDateMatch ? Number.parseInt(fullDateMatch[2], 10) : now.getMonth() + 1;
|
|
433
|
+
const day = fullDateMatch ? Number.parseInt(fullDateMatch[3], 10) : dayOnlyMatch ? Number.parseInt(dayOnlyMatch[1], 10) : NaN;
|
|
434
|
+
const parsed = new Date(year, month - 1, day);
|
|
435
|
+
if (!(Number.isInteger(day) && parsed.getFullYear() === year && parsed.getMonth() === month - 1 && parsed.getDate() === day)) throw new Error("ONES: date must be a valid YYYY-MM-DD date or day of current month");
|
|
436
|
+
return {
|
|
437
|
+
date: toLocalDateString(parsed),
|
|
438
|
+
startTime: getLocalStartUnixSeconds(year, month, day)
|
|
439
|
+
};
|
|
440
|
+
}
|
|
353
441
|
function htmlToPlainText(html) {
|
|
354
442
|
return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\n{3,}/g, "\n\n").trim();
|
|
355
443
|
}
|
|
@@ -544,32 +632,35 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
544
632
|
redirect: "manual"
|
|
545
633
|
})).headers.get("location");
|
|
546
634
|
if (!authorizeLocation) throw new Error("ONES: Authorize response missing location header");
|
|
547
|
-
|
|
548
|
-
if (!
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
635
|
+
let code = parseAuthorizationCode(authorizeLocation);
|
|
636
|
+
if (!code) {
|
|
637
|
+
const authRequestId = parseAuthorizeRequestId(authorizeLocation);
|
|
638
|
+
if (!authRequestId) throw new Error("ONES: Cannot parse auth_request_id from authorize redirect");
|
|
639
|
+
const finalizeRes = await fetch(`${baseUrl}/identity/api/auth_request/finalize`, {
|
|
640
|
+
method: "POST",
|
|
641
|
+
headers: {
|
|
642
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
643
|
+
"Cookie": cookies
|
|
644
|
+
},
|
|
645
|
+
body: JSON.stringify({
|
|
646
|
+
auth_request_id: authRequestId,
|
|
647
|
+
region_uuid: orgUser.region_uuid,
|
|
648
|
+
org_uuid: orgUser.org_uuid,
|
|
649
|
+
org_user_uuid: orgUser.org_user.org_user_uuid
|
|
650
|
+
})
|
|
651
|
+
});
|
|
652
|
+
if (!finalizeRes.ok) {
|
|
653
|
+
const text = await finalizeRes.text().catch(() => "");
|
|
654
|
+
throw new Error(`ONES: Finalize failed: ${finalizeRes.status} ${text}`);
|
|
655
|
+
}
|
|
656
|
+
const callbackLocation = (await fetch(`${baseUrl}/identity/authorize/callback?id=${authRequestId}&lang=zh`, {
|
|
657
|
+
method: "GET",
|
|
658
|
+
headers: { Cookie: cookies },
|
|
659
|
+
redirect: "manual"
|
|
660
|
+
})).headers.get("location");
|
|
661
|
+
if (!callbackLocation) throw new Error("ONES: Callback response missing location header");
|
|
662
|
+
code = parseAuthorizationCode(callbackLocation);
|
|
565
663
|
}
|
|
566
|
-
const callbackLocation = (await fetch(`${baseUrl}/identity/authorize/callback?id=${authRequestId}&lang=zh`, {
|
|
567
|
-
method: "GET",
|
|
568
|
-
headers: { Cookie: cookies },
|
|
569
|
-
redirect: "manual"
|
|
570
|
-
})).headers.get("location");
|
|
571
|
-
if (!callbackLocation) throw new Error("ONES: Callback response missing location header");
|
|
572
|
-
const code = new URL(callbackLocation).searchParams.get("code");
|
|
573
664
|
if (!code) throw new Error("ONES: Cannot parse authorization code from callback redirect");
|
|
574
665
|
const tokenRes = await fetch(`${baseUrl}/identity/oauth/token`, {
|
|
575
666
|
method: "POST",
|
|
@@ -639,11 +730,107 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
639
730
|
}
|
|
640
731
|
return response.json();
|
|
641
732
|
}
|
|
733
|
+
async searchTaskByNumber(taskNumber) {
|
|
734
|
+
const session = await this.login();
|
|
735
|
+
const url = `${this.config.apiBase}/project/api/project/team/${session.teamUuid}/search?q=${encodeURIComponent(String(taskNumber))}&start=0&limit=10&types=task`;
|
|
736
|
+
const response = await fetch(url, { headers: { Authorization: `Bearer ${session.accessToken}` } });
|
|
737
|
+
if (!response.ok) return null;
|
|
738
|
+
const found = ((await response.json()).datas?.task ?? []).map((item) => item.fields).find((fields) => fields?.uuid && fields.number === taskNumber);
|
|
739
|
+
if (!found?.uuid) return null;
|
|
740
|
+
return {
|
|
741
|
+
key: `task-${found.uuid}`,
|
|
742
|
+
uuid: found.uuid,
|
|
743
|
+
number: found.number ?? taskNumber,
|
|
744
|
+
name: found.summary ?? "",
|
|
745
|
+
status: {
|
|
746
|
+
uuid: "",
|
|
747
|
+
name: "",
|
|
748
|
+
category: void 0
|
|
749
|
+
},
|
|
750
|
+
issueType: found.issue_type_uuid || found.issue_type_name ? {
|
|
751
|
+
uuid: found.issue_type_uuid ?? "",
|
|
752
|
+
name: found.issue_type_name ?? ""
|
|
753
|
+
} : void 0,
|
|
754
|
+
project: found.project_uuid || found.project_name ? {
|
|
755
|
+
uuid: found.project_uuid ?? "",
|
|
756
|
+
name: found.project_name ?? ""
|
|
757
|
+
} : void 0
|
|
758
|
+
};
|
|
759
|
+
}
|
|
642
760
|
async fetchIssueTypes() {
|
|
643
761
|
if (this.issueTypesCache) return this.issueTypesCache;
|
|
644
762
|
this.issueTypesCache = (await this.graphql(ISSUE_TYPES_QUERY, { orderBy: { namePinyin: "ASC" } }, "issueTypes")).data?.issueTypes ?? [];
|
|
645
763
|
return this.issueTypesCache;
|
|
646
764
|
}
|
|
765
|
+
async fetchProjects() {
|
|
766
|
+
return (await this.graphql(PROJECTS_QUERY, {
|
|
767
|
+
projectOrderBy: {
|
|
768
|
+
isPin: "DESC",
|
|
769
|
+
namePinyin: "ASC",
|
|
770
|
+
createTime: "DESC"
|
|
771
|
+
},
|
|
772
|
+
projectFilterGroup: [{
|
|
773
|
+
visibleInProject_equal: true,
|
|
774
|
+
isArchive_equal: false
|
|
775
|
+
}],
|
|
776
|
+
groupBy: { projects: {} },
|
|
777
|
+
orderBy: null,
|
|
778
|
+
pagination: {
|
|
779
|
+
limit: 50,
|
|
780
|
+
after: "",
|
|
781
|
+
preciseCount: true
|
|
782
|
+
}
|
|
783
|
+
}, "projects-group-list-for-project-view")).data?.buckets?.flatMap((bucket) => bucket.projects ?? []) ?? [];
|
|
784
|
+
}
|
|
785
|
+
async findTaskByNumber(taskNumber, projectUuid) {
|
|
786
|
+
const filter = { number_in: [taskNumber] };
|
|
787
|
+
if (projectUuid) filter.project_in = [projectUuid];
|
|
788
|
+
const found = ((await this.graphql(TASK_BY_NUMBER_QUERY, {
|
|
789
|
+
groupBy: { tasks: {} },
|
|
790
|
+
groupOrderBy: null,
|
|
791
|
+
orderBy: { createTime: "DESC" },
|
|
792
|
+
filterGroup: [filter],
|
|
793
|
+
search: null,
|
|
794
|
+
pagination: {
|
|
795
|
+
limit: 10,
|
|
796
|
+
preciseCount: false
|
|
797
|
+
},
|
|
798
|
+
limit: 10
|
|
799
|
+
}, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? []).find((task) => task.number === taskNumber && (!projectUuid || task.project?.uuid === projectUuid));
|
|
800
|
+
if (found) return found;
|
|
801
|
+
if (projectUuid) return null;
|
|
802
|
+
return this.searchTaskByNumber(taskNumber);
|
|
803
|
+
}
|
|
804
|
+
async resolveTaskRef(input) {
|
|
805
|
+
const taskId = input.trim();
|
|
806
|
+
if (!taskId) throw new Error("ONES: taskId is required");
|
|
807
|
+
const numMatch = taskId.match(/^#?(\d+)$/);
|
|
808
|
+
if (numMatch) {
|
|
809
|
+
const taskNumber = Number.parseInt(numMatch[1], 10);
|
|
810
|
+
const found = await this.findTaskByNumber(taskNumber);
|
|
811
|
+
if (!found) throw new Error(`ONES: Task #${taskNumber} not found in current team`);
|
|
812
|
+
return {
|
|
813
|
+
key: found.key ?? `task-${found.uuid}`,
|
|
814
|
+
uuid: found.uuid
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
const displayId = parseDisplayId(taskId);
|
|
818
|
+
if (displayId) {
|
|
819
|
+
const project = (await this.fetchProjects()).find((item) => item.identifier?.toLowerCase() === displayId.identifier.toLowerCase());
|
|
820
|
+
if (!project) throw new Error(`ONES: Project identifier "${displayId.identifier}" not found in current team`);
|
|
821
|
+
const found = await this.findTaskByNumber(displayId.number, project.uuid);
|
|
822
|
+
if (!found) throw new Error(`ONES: Task "${taskId}" not found in current team`);
|
|
823
|
+
return {
|
|
824
|
+
key: found.key ?? `task-${found.uuid}`,
|
|
825
|
+
uuid: found.uuid
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
const key = taskId.startsWith("task-") ? taskId : `task-${taskId}`;
|
|
829
|
+
return {
|
|
830
|
+
key,
|
|
831
|
+
uuid: key.slice(5)
|
|
832
|
+
};
|
|
833
|
+
}
|
|
647
834
|
async searchTeamUsers(keyword) {
|
|
648
835
|
const session = await this.login();
|
|
649
836
|
const url = `${this.config.apiBase}/project/api/project/team/${session.teamUuid}/users/search`;
|
|
@@ -832,27 +1019,9 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
832
1019
|
};
|
|
833
1020
|
}
|
|
834
1021
|
if (isOnesWikiUrlInput(params.id)) throw new Error("ONES: Unsupported wiki page URL. Expected /wiki/#/team/{teamUuid}/space/{spaceUuid}/page/{wikiUuid}");
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
if (
|
|
838
|
-
const taskNumber = Number.parseInt(numMatch[1], 10);
|
|
839
|
-
const found = ((await this.graphql(TASK_BY_NUMBER_QUERY, {
|
|
840
|
-
groupBy: { tasks: {} },
|
|
841
|
-
groupOrderBy: null,
|
|
842
|
-
orderBy: { createTime: "DESC" },
|
|
843
|
-
filterGroup: [{ number_in: [taskNumber] }],
|
|
844
|
-
search: null,
|
|
845
|
-
pagination: {
|
|
846
|
-
limit: 10,
|
|
847
|
-
preciseCount: false
|
|
848
|
-
},
|
|
849
|
-
limit: 10
|
|
850
|
-
}, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? []).find((t) => t.number === taskNumber);
|
|
851
|
-
if (!found) throw new Error(`ONES: Task #${taskNumber} not found in current team`);
|
|
852
|
-
taskUuid = found.uuid;
|
|
853
|
-
}
|
|
854
|
-
const task = (await this.graphql(TASK_DETAIL_QUERY, { key: `task-${taskUuid}` }, "Task")).data?.task;
|
|
855
|
-
if (!task) throw new Error(`ONES: Task "${taskUuid}" not found`);
|
|
1022
|
+
const taskRef = await this.resolveTaskRef(params.id);
|
|
1023
|
+
const task = (await this.graphql(TASK_DETAIL_QUERY, { key: taskRef.key }, "Task")).data?.task;
|
|
1024
|
+
if (!task) throw new Error(`ONES: Task "${params.id}" not found`);
|
|
856
1025
|
const wikiRefs = /* @__PURE__ */ new Map();
|
|
857
1026
|
for (const wiki of task.relatedWikiPages ?? []) if (!wiki.errorMessage) wikiRefs.set(wiki.uuid, {
|
|
858
1027
|
title: wiki.title,
|
|
@@ -987,6 +1156,69 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
987
1156
|
pageSize
|
|
988
1157
|
};
|
|
989
1158
|
}
|
|
1159
|
+
async addManhour(params) {
|
|
1160
|
+
const description = params.description.trim();
|
|
1161
|
+
if (!description) throw new Error("ONES: description is required");
|
|
1162
|
+
const taskRef = await this.resolveTaskRef(params.taskId);
|
|
1163
|
+
const onesHours = toOnesHours(params.hours);
|
|
1164
|
+
const workDate = parseManhourDate(params.date);
|
|
1165
|
+
const key = (await this.graphql(ADD_MANHOUR_MUTATION, {
|
|
1166
|
+
mode: "simple",
|
|
1167
|
+
type: "recorded",
|
|
1168
|
+
customData: {},
|
|
1169
|
+
owner: (await this.login()).userUuid,
|
|
1170
|
+
task: taskRef.uuid,
|
|
1171
|
+
start_time: workDate.startTime,
|
|
1172
|
+
hours: onesHours,
|
|
1173
|
+
description
|
|
1174
|
+
}, "add-manhour")).data?.addManhour?.key;
|
|
1175
|
+
if (!key) throw new Error("ONES: Failed to add manhour");
|
|
1176
|
+
return {
|
|
1177
|
+
key,
|
|
1178
|
+
taskUuid: taskRef.uuid,
|
|
1179
|
+
hours: params.hours,
|
|
1180
|
+
description,
|
|
1181
|
+
date: workDate.date
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
async updateTaskPlanDates(params) {
|
|
1185
|
+
const planStartDate = params.planStartDate?.trim();
|
|
1186
|
+
const planEndDate = params.planEndDate?.trim();
|
|
1187
|
+
if (!planStartDate && !planEndDate) throw new Error("ONES: planStartDate or planEndDate is required");
|
|
1188
|
+
if (planStartDate && !isValidOnesDate(planStartDate)) throw new Error("ONES: planStartDate must be a valid YYYY-MM-DD date");
|
|
1189
|
+
if (planEndDate && !isValidOnesDate(planEndDate)) throw new Error("ONES: planEndDate must be a valid YYYY-MM-DD date");
|
|
1190
|
+
const taskRef = await this.resolveTaskRef(params.taskId);
|
|
1191
|
+
const session = await this.login();
|
|
1192
|
+
const fieldValues = [];
|
|
1193
|
+
if (planStartDate) fieldValues.push({
|
|
1194
|
+
field_uuid: "field027",
|
|
1195
|
+
value: planStartDate
|
|
1196
|
+
});
|
|
1197
|
+
if (planEndDate) fieldValues.push({
|
|
1198
|
+
field_uuid: "field028",
|
|
1199
|
+
value: planEndDate
|
|
1200
|
+
});
|
|
1201
|
+
const response = await fetch(`${this.config.apiBase}/project/api/project/team/${session.teamUuid}/tasks/update3`, {
|
|
1202
|
+
method: "POST",
|
|
1203
|
+
headers: {
|
|
1204
|
+
"Authorization": `Bearer ${session.accessToken}`,
|
|
1205
|
+
"Content-Type": "application/json"
|
|
1206
|
+
},
|
|
1207
|
+
body: JSON.stringify({ tasks: [{
|
|
1208
|
+
uuid: taskRef.uuid,
|
|
1209
|
+
field_values: fieldValues
|
|
1210
|
+
}] })
|
|
1211
|
+
});
|
|
1212
|
+
if (!response.ok) {
|
|
1213
|
+
const text = await response.text().catch(() => "");
|
|
1214
|
+
throw new Error(`ONES: Failed to update task plan dates: ${response.status} ${text}`);
|
|
1215
|
+
}
|
|
1216
|
+
return {
|
|
1217
|
+
taskUuid: taskRef.uuid,
|
|
1218
|
+
planStartDate: planStartDate ?? null,
|
|
1219
|
+
planEndDate: planEndDate ?? null
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
990
1222
|
async getRelatedIssues(params) {
|
|
991
1223
|
const session = await this.login();
|
|
992
1224
|
const taskKey = params.taskId.startsWith("task-") ? params.taskId : `task-${params.taskId}`;
|
|
@@ -1334,6 +1566,42 @@ function loadConfig(startDir) {
|
|
|
1334
1566
|
};
|
|
1335
1567
|
}
|
|
1336
1568
|
|
|
1569
|
+
//#endregion
|
|
1570
|
+
//#region src/tools/add-manhour.ts
|
|
1571
|
+
const AddManhourSchema = zod_v4.z.object({
|
|
1572
|
+
taskId: zod_v4.z.string().min(1).describe("The task or requirement ID, key, number, or displayId (e.g. \"task-mock-uuid\", \"mock-uuid\", \"1001\", or \"DEMO-1001\")"),
|
|
1573
|
+
hours: zod_v4.z.number().positive().describe("Work hours to record. Natural hours are converted to ONES internal units."),
|
|
1574
|
+
description: zod_v4.z.string().min(1).describe("Work log description."),
|
|
1575
|
+
date: zod_v4.z.string().optional().describe("Optional work date. Accepts YYYY-MM-DD, a day number like \"11\", or a day-of-month phrase like \"11号\"; day-only values use the current year and month."),
|
|
1576
|
+
source: zod_v4.z.string().optional().describe("Source to update. If omitted, uses the default source.")
|
|
1577
|
+
});
|
|
1578
|
+
async function handleAddManhour(input, adapters, defaultSource) {
|
|
1579
|
+
const sourceType = input.source ?? defaultSource;
|
|
1580
|
+
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
1581
|
+
const adapter = adapters.get(sourceType);
|
|
1582
|
+
if (!adapter) throw new Error(`Source "${sourceType}" is not configured. Available: ${[...adapters.keys()].join(", ")}`);
|
|
1583
|
+
return { content: [{
|
|
1584
|
+
type: "text",
|
|
1585
|
+
text: formatAddManhourResult(await adapter.addManhour({
|
|
1586
|
+
taskId: input.taskId,
|
|
1587
|
+
hours: input.hours,
|
|
1588
|
+
description: input.description,
|
|
1589
|
+
date: input.date
|
|
1590
|
+
}))
|
|
1591
|
+
}] };
|
|
1592
|
+
}
|
|
1593
|
+
function formatAddManhourResult(result) {
|
|
1594
|
+
return [
|
|
1595
|
+
"Added manhour.",
|
|
1596
|
+
"",
|
|
1597
|
+
`- **Key**: ${result.key}`,
|
|
1598
|
+
`- **Task UUID**: ${result.taskUuid}`,
|
|
1599
|
+
`- **Hours**: ${result.hours}`,
|
|
1600
|
+
`- **Date**: ${result.date ?? "today"}`,
|
|
1601
|
+
`- **Description**: ${result.description}`
|
|
1602
|
+
].join("\n");
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1337
1605
|
//#endregion
|
|
1338
1606
|
//#region src/tools/get-issue-detail.ts
|
|
1339
1607
|
const GetIssueDetailSchema = zod_v4.z.object({
|
|
@@ -1648,6 +1916,40 @@ async function handleSearchRequirements(input, adapters, defaultSource) {
|
|
|
1648
1916
|
}] };
|
|
1649
1917
|
}
|
|
1650
1918
|
|
|
1919
|
+
//#endregion
|
|
1920
|
+
//#region src/tools/update-task-plan-dates.ts
|
|
1921
|
+
const DateSchema = zod_v4.z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Expected YYYY-MM-DD");
|
|
1922
|
+
const UpdateTaskPlanDatesSchema = zod_v4.z.object({
|
|
1923
|
+
taskId: zod_v4.z.string().min(1).describe("The task or requirement ID, key, number, or displayId (e.g. \"task-mock-uuid\", \"mock-uuid\", \"1001\", or \"DEMO-1001\")"),
|
|
1924
|
+
planStartDate: DateSchema.optional().describe("Plan start date in YYYY-MM-DD format."),
|
|
1925
|
+
planEndDate: DateSchema.optional().describe("Plan end date in YYYY-MM-DD format."),
|
|
1926
|
+
source: zod_v4.z.string().optional().describe("Source to update. If omitted, uses the default source.")
|
|
1927
|
+
});
|
|
1928
|
+
async function handleUpdateTaskPlanDates(input, adapters, defaultSource) {
|
|
1929
|
+
const sourceType = input.source ?? defaultSource;
|
|
1930
|
+
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
1931
|
+
const adapter = adapters.get(sourceType);
|
|
1932
|
+
if (!adapter) throw new Error(`Source "${sourceType}" is not configured. Available: ${[...adapters.keys()].join(", ")}`);
|
|
1933
|
+
return { content: [{
|
|
1934
|
+
type: "text",
|
|
1935
|
+
text: formatUpdateTaskPlanDatesResult(await adapter.updateTaskPlanDates({
|
|
1936
|
+
taskId: input.taskId,
|
|
1937
|
+
planStartDate: input.planStartDate,
|
|
1938
|
+
planEndDate: input.planEndDate
|
|
1939
|
+
}))
|
|
1940
|
+
}] };
|
|
1941
|
+
}
|
|
1942
|
+
function formatUpdateTaskPlanDatesResult(result) {
|
|
1943
|
+
const lines = [
|
|
1944
|
+
"Updated task plan dates.",
|
|
1945
|
+
"",
|
|
1946
|
+
`- **Task UUID**: ${result.taskUuid}`
|
|
1947
|
+
];
|
|
1948
|
+
if (result.planStartDate) lines.push(`- **Plan Start Date**: ${result.planStartDate}`);
|
|
1949
|
+
if (result.planEndDate) lines.push(`- **Plan End Date**: ${result.planEndDate}`);
|
|
1950
|
+
return lines.join("\n");
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1651
1953
|
//#endregion
|
|
1652
1954
|
//#region src/index.ts
|
|
1653
1955
|
/**
|
|
@@ -1773,6 +2075,32 @@ async function main() {
|
|
|
1773
2075
|
};
|
|
1774
2076
|
}
|
|
1775
2077
|
});
|
|
2078
|
+
server.tool("add_manhour", "Add a work-hour record to a ONES task, bug, or requirement. Supports task key, uuid, number, or displayId.", AddManhourSchema.shape, async (params) => {
|
|
2079
|
+
try {
|
|
2080
|
+
return await handleAddManhour(params, adapters, config.config.defaultSource);
|
|
2081
|
+
} catch (err) {
|
|
2082
|
+
return {
|
|
2083
|
+
content: [{
|
|
2084
|
+
type: "text",
|
|
2085
|
+
text: `Error: ${err.message}`
|
|
2086
|
+
}],
|
|
2087
|
+
isError: true
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
server.tool("update_task_plan_dates", "Update plan start and/or plan end dates for a ONES task, bug, or requirement. Supports task key, uuid, number, or displayId.", UpdateTaskPlanDatesSchema.shape, async (params) => {
|
|
2092
|
+
try {
|
|
2093
|
+
return await handleUpdateTaskPlanDates(params, adapters, config.config.defaultSource);
|
|
2094
|
+
} catch (err) {
|
|
2095
|
+
return {
|
|
2096
|
+
content: [{
|
|
2097
|
+
type: "text",
|
|
2098
|
+
text: `Error: ${err.message}`
|
|
2099
|
+
}],
|
|
2100
|
+
isError: true
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
});
|
|
1776
2104
|
const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
|
|
1777
2105
|
await server.connect(transport);
|
|
1778
2106
|
}
|