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.mjs
CHANGED
|
@@ -116,6 +116,26 @@ const ISSUE_TYPES_QUERY = `
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
`;
|
|
119
|
+
const PROJECTS_QUERY = `
|
|
120
|
+
query Projects($groupBy: GroupBy, $orderBy: OrderBy, $pagination: Pagination, $projectOrderBy: OrderBy, $projectFilterGroup: [Filter!]) {
|
|
121
|
+
buckets(groupBy: $groupBy, orderBy: $orderBy, pagination: $pagination) {
|
|
122
|
+
key
|
|
123
|
+
projects(limit: 10000, orderBy: $projectOrderBy, filterGroup: $projectFilterGroup) {
|
|
124
|
+
key
|
|
125
|
+
uuid
|
|
126
|
+
name
|
|
127
|
+
identifier
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
const ADD_MANHOUR_MUTATION = `
|
|
133
|
+
mutation AddManhour {
|
|
134
|
+
addManhour(mode: $mode, owner: $owner, task: $task, type: $type, start_time: $start_time, hours: $hours, description: $description, customData: $customData) {
|
|
135
|
+
key
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
`;
|
|
119
139
|
const TASK_BY_NUMBER_QUERY = SEARCH_TASKS_QUERY;
|
|
120
140
|
const RELATED_TASKS_QUERY = `
|
|
121
141
|
query Task($key: Key) {
|
|
@@ -322,6 +342,74 @@ function parseOnesWikiPageRoute(input) {
|
|
|
322
342
|
function isOnesWikiUrlInput(input) {
|
|
323
343
|
return input.includes("/wiki/");
|
|
324
344
|
}
|
|
345
|
+
function parseAuthorizeRequestId(location) {
|
|
346
|
+
try {
|
|
347
|
+
const parsed = new URL(location);
|
|
348
|
+
return parsed.searchParams.get("auth_request_id") ?? parsed.searchParams.get("id");
|
|
349
|
+
} catch {
|
|
350
|
+
const match = location.match(/[?&](?:auth_request_id|id)=([^&#]+)/);
|
|
351
|
+
return match?.[1] ? decodeURIComponent(match[1]) : null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function parseAuthorizationCode(location) {
|
|
355
|
+
try {
|
|
356
|
+
return new URL(location).searchParams.get("code");
|
|
357
|
+
} catch {
|
|
358
|
+
const match = location.match(/[?&]code=([^&#]+)/);
|
|
359
|
+
return match?.[1] ? decodeURIComponent(match[1]) : null;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function parseDisplayId(input) {
|
|
363
|
+
const match = input.trim().match(/^([a-z]\w*)-(\d+)$/i);
|
|
364
|
+
if (!match?.[1] || !match[2]) return null;
|
|
365
|
+
return {
|
|
366
|
+
identifier: match[1],
|
|
367
|
+
number: Number.parseInt(match[2], 10)
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function isValidOnesDate(value) {
|
|
371
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
372
|
+
const [yearText, monthText, dayText] = value.split("-");
|
|
373
|
+
const year = Number.parseInt(yearText, 10);
|
|
374
|
+
const month = Number.parseInt(monthText, 10);
|
|
375
|
+
const day = Number.parseInt(dayText, 10);
|
|
376
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
377
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
378
|
+
}
|
|
379
|
+
function toOnesHours(hours) {
|
|
380
|
+
if (!Number.isFinite(hours) || hours <= 0) throw new Error("ONES: hours must be a positive number");
|
|
381
|
+
return Math.round(hours * 1e5);
|
|
382
|
+
}
|
|
383
|
+
function getTodayStartUnixSeconds() {
|
|
384
|
+
const now = /* @__PURE__ */ new Date();
|
|
385
|
+
const localStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
386
|
+
return Math.floor(localStart.getTime() / 1e3);
|
|
387
|
+
}
|
|
388
|
+
function toLocalDateString(date) {
|
|
389
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
390
|
+
}
|
|
391
|
+
function getLocalStartUnixSeconds(year, month, day) {
|
|
392
|
+
return Math.floor(new Date(year, month - 1, day).getTime() / 1e3);
|
|
393
|
+
}
|
|
394
|
+
function parseManhourDate(input) {
|
|
395
|
+
const value = input?.trim();
|
|
396
|
+
if (!value) return {
|
|
397
|
+
date: null,
|
|
398
|
+
startTime: getTodayStartUnixSeconds()
|
|
399
|
+
};
|
|
400
|
+
const fullDateMatch = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
401
|
+
const dayOnlyMatch = value.match(/^(\d{1,2})号?$/);
|
|
402
|
+
const now = /* @__PURE__ */ new Date();
|
|
403
|
+
const year = fullDateMatch ? Number.parseInt(fullDateMatch[1], 10) : now.getFullYear();
|
|
404
|
+
const month = fullDateMatch ? Number.parseInt(fullDateMatch[2], 10) : now.getMonth() + 1;
|
|
405
|
+
const day = fullDateMatch ? Number.parseInt(fullDateMatch[3], 10) : dayOnlyMatch ? Number.parseInt(dayOnlyMatch[1], 10) : NaN;
|
|
406
|
+
const parsed = new Date(year, month - 1, day);
|
|
407
|
+
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");
|
|
408
|
+
return {
|
|
409
|
+
date: toLocalDateString(parsed),
|
|
410
|
+
startTime: getLocalStartUnixSeconds(year, month, day)
|
|
411
|
+
};
|
|
412
|
+
}
|
|
325
413
|
function htmlToPlainText(html) {
|
|
326
414
|
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();
|
|
327
415
|
}
|
|
@@ -516,32 +604,35 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
516
604
|
redirect: "manual"
|
|
517
605
|
})).headers.get("location");
|
|
518
606
|
if (!authorizeLocation) throw new Error("ONES: Authorize response missing location header");
|
|
519
|
-
|
|
520
|
-
if (!
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
607
|
+
let code = parseAuthorizationCode(authorizeLocation);
|
|
608
|
+
if (!code) {
|
|
609
|
+
const authRequestId = parseAuthorizeRequestId(authorizeLocation);
|
|
610
|
+
if (!authRequestId) throw new Error("ONES: Cannot parse auth_request_id from authorize redirect");
|
|
611
|
+
const finalizeRes = await fetch(`${baseUrl}/identity/api/auth_request/finalize`, {
|
|
612
|
+
method: "POST",
|
|
613
|
+
headers: {
|
|
614
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
615
|
+
"Cookie": cookies
|
|
616
|
+
},
|
|
617
|
+
body: JSON.stringify({
|
|
618
|
+
auth_request_id: authRequestId,
|
|
619
|
+
region_uuid: orgUser.region_uuid,
|
|
620
|
+
org_uuid: orgUser.org_uuid,
|
|
621
|
+
org_user_uuid: orgUser.org_user.org_user_uuid
|
|
622
|
+
})
|
|
623
|
+
});
|
|
624
|
+
if (!finalizeRes.ok) {
|
|
625
|
+
const text = await finalizeRes.text().catch(() => "");
|
|
626
|
+
throw new Error(`ONES: Finalize failed: ${finalizeRes.status} ${text}`);
|
|
627
|
+
}
|
|
628
|
+
const callbackLocation = (await fetch(`${baseUrl}/identity/authorize/callback?id=${authRequestId}&lang=zh`, {
|
|
629
|
+
method: "GET",
|
|
630
|
+
headers: { Cookie: cookies },
|
|
631
|
+
redirect: "manual"
|
|
632
|
+
})).headers.get("location");
|
|
633
|
+
if (!callbackLocation) throw new Error("ONES: Callback response missing location header");
|
|
634
|
+
code = parseAuthorizationCode(callbackLocation);
|
|
537
635
|
}
|
|
538
|
-
const callbackLocation = (await fetch(`${baseUrl}/identity/authorize/callback?id=${authRequestId}&lang=zh`, {
|
|
539
|
-
method: "GET",
|
|
540
|
-
headers: { Cookie: cookies },
|
|
541
|
-
redirect: "manual"
|
|
542
|
-
})).headers.get("location");
|
|
543
|
-
if (!callbackLocation) throw new Error("ONES: Callback response missing location header");
|
|
544
|
-
const code = new URL(callbackLocation).searchParams.get("code");
|
|
545
636
|
if (!code) throw new Error("ONES: Cannot parse authorization code from callback redirect");
|
|
546
637
|
const tokenRes = await fetch(`${baseUrl}/identity/oauth/token`, {
|
|
547
638
|
method: "POST",
|
|
@@ -611,11 +702,107 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
611
702
|
}
|
|
612
703
|
return response.json();
|
|
613
704
|
}
|
|
705
|
+
async searchTaskByNumber(taskNumber) {
|
|
706
|
+
const session = await this.login();
|
|
707
|
+
const url = `${this.config.apiBase}/project/api/project/team/${session.teamUuid}/search?q=${encodeURIComponent(String(taskNumber))}&start=0&limit=10&types=task`;
|
|
708
|
+
const response = await fetch(url, { headers: { Authorization: `Bearer ${session.accessToken}` } });
|
|
709
|
+
if (!response.ok) return null;
|
|
710
|
+
const found = ((await response.json()).datas?.task ?? []).map((item) => item.fields).find((fields) => fields?.uuid && fields.number === taskNumber);
|
|
711
|
+
if (!found?.uuid) return null;
|
|
712
|
+
return {
|
|
713
|
+
key: `task-${found.uuid}`,
|
|
714
|
+
uuid: found.uuid,
|
|
715
|
+
number: found.number ?? taskNumber,
|
|
716
|
+
name: found.summary ?? "",
|
|
717
|
+
status: {
|
|
718
|
+
uuid: "",
|
|
719
|
+
name: "",
|
|
720
|
+
category: void 0
|
|
721
|
+
},
|
|
722
|
+
issueType: found.issue_type_uuid || found.issue_type_name ? {
|
|
723
|
+
uuid: found.issue_type_uuid ?? "",
|
|
724
|
+
name: found.issue_type_name ?? ""
|
|
725
|
+
} : void 0,
|
|
726
|
+
project: found.project_uuid || found.project_name ? {
|
|
727
|
+
uuid: found.project_uuid ?? "",
|
|
728
|
+
name: found.project_name ?? ""
|
|
729
|
+
} : void 0
|
|
730
|
+
};
|
|
731
|
+
}
|
|
614
732
|
async fetchIssueTypes() {
|
|
615
733
|
if (this.issueTypesCache) return this.issueTypesCache;
|
|
616
734
|
this.issueTypesCache = (await this.graphql(ISSUE_TYPES_QUERY, { orderBy: { namePinyin: "ASC" } }, "issueTypes")).data?.issueTypes ?? [];
|
|
617
735
|
return this.issueTypesCache;
|
|
618
736
|
}
|
|
737
|
+
async fetchProjects() {
|
|
738
|
+
return (await this.graphql(PROJECTS_QUERY, {
|
|
739
|
+
projectOrderBy: {
|
|
740
|
+
isPin: "DESC",
|
|
741
|
+
namePinyin: "ASC",
|
|
742
|
+
createTime: "DESC"
|
|
743
|
+
},
|
|
744
|
+
projectFilterGroup: [{
|
|
745
|
+
visibleInProject_equal: true,
|
|
746
|
+
isArchive_equal: false
|
|
747
|
+
}],
|
|
748
|
+
groupBy: { projects: {} },
|
|
749
|
+
orderBy: null,
|
|
750
|
+
pagination: {
|
|
751
|
+
limit: 50,
|
|
752
|
+
after: "",
|
|
753
|
+
preciseCount: true
|
|
754
|
+
}
|
|
755
|
+
}, "projects-group-list-for-project-view")).data?.buckets?.flatMap((bucket) => bucket.projects ?? []) ?? [];
|
|
756
|
+
}
|
|
757
|
+
async findTaskByNumber(taskNumber, projectUuid) {
|
|
758
|
+
const filter = { number_in: [taskNumber] };
|
|
759
|
+
if (projectUuid) filter.project_in = [projectUuid];
|
|
760
|
+
const found = ((await this.graphql(TASK_BY_NUMBER_QUERY, {
|
|
761
|
+
groupBy: { tasks: {} },
|
|
762
|
+
groupOrderBy: null,
|
|
763
|
+
orderBy: { createTime: "DESC" },
|
|
764
|
+
filterGroup: [filter],
|
|
765
|
+
search: null,
|
|
766
|
+
pagination: {
|
|
767
|
+
limit: 10,
|
|
768
|
+
preciseCount: false
|
|
769
|
+
},
|
|
770
|
+
limit: 10
|
|
771
|
+
}, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? []).find((task) => task.number === taskNumber && (!projectUuid || task.project?.uuid === projectUuid));
|
|
772
|
+
if (found) return found;
|
|
773
|
+
if (projectUuid) return null;
|
|
774
|
+
return this.searchTaskByNumber(taskNumber);
|
|
775
|
+
}
|
|
776
|
+
async resolveTaskRef(input) {
|
|
777
|
+
const taskId = input.trim();
|
|
778
|
+
if (!taskId) throw new Error("ONES: taskId is required");
|
|
779
|
+
const numMatch = taskId.match(/^#?(\d+)$/);
|
|
780
|
+
if (numMatch) {
|
|
781
|
+
const taskNumber = Number.parseInt(numMatch[1], 10);
|
|
782
|
+
const found = await this.findTaskByNumber(taskNumber);
|
|
783
|
+
if (!found) throw new Error(`ONES: Task #${taskNumber} not found in current team`);
|
|
784
|
+
return {
|
|
785
|
+
key: found.key ?? `task-${found.uuid}`,
|
|
786
|
+
uuid: found.uuid
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
const displayId = parseDisplayId(taskId);
|
|
790
|
+
if (displayId) {
|
|
791
|
+
const project = (await this.fetchProjects()).find((item) => item.identifier?.toLowerCase() === displayId.identifier.toLowerCase());
|
|
792
|
+
if (!project) throw new Error(`ONES: Project identifier "${displayId.identifier}" not found in current team`);
|
|
793
|
+
const found = await this.findTaskByNumber(displayId.number, project.uuid);
|
|
794
|
+
if (!found) throw new Error(`ONES: Task "${taskId}" not found in current team`);
|
|
795
|
+
return {
|
|
796
|
+
key: found.key ?? `task-${found.uuid}`,
|
|
797
|
+
uuid: found.uuid
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
const key = taskId.startsWith("task-") ? taskId : `task-${taskId}`;
|
|
801
|
+
return {
|
|
802
|
+
key,
|
|
803
|
+
uuid: key.slice(5)
|
|
804
|
+
};
|
|
805
|
+
}
|
|
619
806
|
async searchTeamUsers(keyword) {
|
|
620
807
|
const session = await this.login();
|
|
621
808
|
const url = `${this.config.apiBase}/project/api/project/team/${session.teamUuid}/users/search`;
|
|
@@ -804,27 +991,9 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
804
991
|
};
|
|
805
992
|
}
|
|
806
993
|
if (isOnesWikiUrlInput(params.id)) throw new Error("ONES: Unsupported wiki page URL. Expected /wiki/#/team/{teamUuid}/space/{spaceUuid}/page/{wikiUuid}");
|
|
807
|
-
|
|
808
|
-
const
|
|
809
|
-
if (
|
|
810
|
-
const taskNumber = Number.parseInt(numMatch[1], 10);
|
|
811
|
-
const found = ((await this.graphql(TASK_BY_NUMBER_QUERY, {
|
|
812
|
-
groupBy: { tasks: {} },
|
|
813
|
-
groupOrderBy: null,
|
|
814
|
-
orderBy: { createTime: "DESC" },
|
|
815
|
-
filterGroup: [{ number_in: [taskNumber] }],
|
|
816
|
-
search: null,
|
|
817
|
-
pagination: {
|
|
818
|
-
limit: 10,
|
|
819
|
-
preciseCount: false
|
|
820
|
-
},
|
|
821
|
-
limit: 10
|
|
822
|
-
}, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? []).find((t) => t.number === taskNumber);
|
|
823
|
-
if (!found) throw new Error(`ONES: Task #${taskNumber} not found in current team`);
|
|
824
|
-
taskUuid = found.uuid;
|
|
825
|
-
}
|
|
826
|
-
const task = (await this.graphql(TASK_DETAIL_QUERY, { key: `task-${taskUuid}` }, "Task")).data?.task;
|
|
827
|
-
if (!task) throw new Error(`ONES: Task "${taskUuid}" not found`);
|
|
994
|
+
const taskRef = await this.resolveTaskRef(params.id);
|
|
995
|
+
const task = (await this.graphql(TASK_DETAIL_QUERY, { key: taskRef.key }, "Task")).data?.task;
|
|
996
|
+
if (!task) throw new Error(`ONES: Task "${params.id}" not found`);
|
|
828
997
|
const wikiRefs = /* @__PURE__ */ new Map();
|
|
829
998
|
for (const wiki of task.relatedWikiPages ?? []) if (!wiki.errorMessage) wikiRefs.set(wiki.uuid, {
|
|
830
999
|
title: wiki.title,
|
|
@@ -959,6 +1128,69 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
959
1128
|
pageSize
|
|
960
1129
|
};
|
|
961
1130
|
}
|
|
1131
|
+
async addManhour(params) {
|
|
1132
|
+
const description = params.description.trim();
|
|
1133
|
+
if (!description) throw new Error("ONES: description is required");
|
|
1134
|
+
const taskRef = await this.resolveTaskRef(params.taskId);
|
|
1135
|
+
const onesHours = toOnesHours(params.hours);
|
|
1136
|
+
const workDate = parseManhourDate(params.date);
|
|
1137
|
+
const key = (await this.graphql(ADD_MANHOUR_MUTATION, {
|
|
1138
|
+
mode: "simple",
|
|
1139
|
+
type: "recorded",
|
|
1140
|
+
customData: {},
|
|
1141
|
+
owner: (await this.login()).userUuid,
|
|
1142
|
+
task: taskRef.uuid,
|
|
1143
|
+
start_time: workDate.startTime,
|
|
1144
|
+
hours: onesHours,
|
|
1145
|
+
description
|
|
1146
|
+
}, "add-manhour")).data?.addManhour?.key;
|
|
1147
|
+
if (!key) throw new Error("ONES: Failed to add manhour");
|
|
1148
|
+
return {
|
|
1149
|
+
key,
|
|
1150
|
+
taskUuid: taskRef.uuid,
|
|
1151
|
+
hours: params.hours,
|
|
1152
|
+
description,
|
|
1153
|
+
date: workDate.date
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
async updateTaskPlanDates(params) {
|
|
1157
|
+
const planStartDate = params.planStartDate?.trim();
|
|
1158
|
+
const planEndDate = params.planEndDate?.trim();
|
|
1159
|
+
if (!planStartDate && !planEndDate) throw new Error("ONES: planStartDate or planEndDate is required");
|
|
1160
|
+
if (planStartDate && !isValidOnesDate(planStartDate)) throw new Error("ONES: planStartDate must be a valid YYYY-MM-DD date");
|
|
1161
|
+
if (planEndDate && !isValidOnesDate(planEndDate)) throw new Error("ONES: planEndDate must be a valid YYYY-MM-DD date");
|
|
1162
|
+
const taskRef = await this.resolveTaskRef(params.taskId);
|
|
1163
|
+
const session = await this.login();
|
|
1164
|
+
const fieldValues = [];
|
|
1165
|
+
if (planStartDate) fieldValues.push({
|
|
1166
|
+
field_uuid: "field027",
|
|
1167
|
+
value: planStartDate
|
|
1168
|
+
});
|
|
1169
|
+
if (planEndDate) fieldValues.push({
|
|
1170
|
+
field_uuid: "field028",
|
|
1171
|
+
value: planEndDate
|
|
1172
|
+
});
|
|
1173
|
+
const response = await fetch(`${this.config.apiBase}/project/api/project/team/${session.teamUuid}/tasks/update3`, {
|
|
1174
|
+
method: "POST",
|
|
1175
|
+
headers: {
|
|
1176
|
+
"Authorization": `Bearer ${session.accessToken}`,
|
|
1177
|
+
"Content-Type": "application/json"
|
|
1178
|
+
},
|
|
1179
|
+
body: JSON.stringify({ tasks: [{
|
|
1180
|
+
uuid: taskRef.uuid,
|
|
1181
|
+
field_values: fieldValues
|
|
1182
|
+
}] })
|
|
1183
|
+
});
|
|
1184
|
+
if (!response.ok) {
|
|
1185
|
+
const text = await response.text().catch(() => "");
|
|
1186
|
+
throw new Error(`ONES: Failed to update task plan dates: ${response.status} ${text}`);
|
|
1187
|
+
}
|
|
1188
|
+
return {
|
|
1189
|
+
taskUuid: taskRef.uuid,
|
|
1190
|
+
planStartDate: planStartDate ?? null,
|
|
1191
|
+
planEndDate: planEndDate ?? null
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
962
1194
|
async getRelatedIssues(params) {
|
|
963
1195
|
const session = await this.login();
|
|
964
1196
|
const taskKey = params.taskId.startsWith("task-") ? params.taskId : `task-${params.taskId}`;
|
|
@@ -1306,6 +1538,42 @@ function loadConfig(startDir) {
|
|
|
1306
1538
|
};
|
|
1307
1539
|
}
|
|
1308
1540
|
|
|
1541
|
+
//#endregion
|
|
1542
|
+
//#region src/tools/add-manhour.ts
|
|
1543
|
+
const AddManhourSchema = z.object({
|
|
1544
|
+
taskId: 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\")"),
|
|
1545
|
+
hours: z.number().positive().describe("Work hours to record. Natural hours are converted to ONES internal units."),
|
|
1546
|
+
description: z.string().min(1).describe("Work log description."),
|
|
1547
|
+
date: 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."),
|
|
1548
|
+
source: z.string().optional().describe("Source to update. If omitted, uses the default source.")
|
|
1549
|
+
});
|
|
1550
|
+
async function handleAddManhour(input, adapters, defaultSource) {
|
|
1551
|
+
const sourceType = input.source ?? defaultSource;
|
|
1552
|
+
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
1553
|
+
const adapter = adapters.get(sourceType);
|
|
1554
|
+
if (!adapter) throw new Error(`Source "${sourceType}" is not configured. Available: ${[...adapters.keys()].join(", ")}`);
|
|
1555
|
+
return { content: [{
|
|
1556
|
+
type: "text",
|
|
1557
|
+
text: formatAddManhourResult(await adapter.addManhour({
|
|
1558
|
+
taskId: input.taskId,
|
|
1559
|
+
hours: input.hours,
|
|
1560
|
+
description: input.description,
|
|
1561
|
+
date: input.date
|
|
1562
|
+
}))
|
|
1563
|
+
}] };
|
|
1564
|
+
}
|
|
1565
|
+
function formatAddManhourResult(result) {
|
|
1566
|
+
return [
|
|
1567
|
+
"Added manhour.",
|
|
1568
|
+
"",
|
|
1569
|
+
`- **Key**: ${result.key}`,
|
|
1570
|
+
`- **Task UUID**: ${result.taskUuid}`,
|
|
1571
|
+
`- **Hours**: ${result.hours}`,
|
|
1572
|
+
`- **Date**: ${result.date ?? "today"}`,
|
|
1573
|
+
`- **Description**: ${result.description}`
|
|
1574
|
+
].join("\n");
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1309
1577
|
//#endregion
|
|
1310
1578
|
//#region src/tools/get-issue-detail.ts
|
|
1311
1579
|
const GetIssueDetailSchema = z.object({
|
|
@@ -1620,6 +1888,40 @@ async function handleSearchRequirements(input, adapters, defaultSource) {
|
|
|
1620
1888
|
}] };
|
|
1621
1889
|
}
|
|
1622
1890
|
|
|
1891
|
+
//#endregion
|
|
1892
|
+
//#region src/tools/update-task-plan-dates.ts
|
|
1893
|
+
const DateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Expected YYYY-MM-DD");
|
|
1894
|
+
const UpdateTaskPlanDatesSchema = z.object({
|
|
1895
|
+
taskId: 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\")"),
|
|
1896
|
+
planStartDate: DateSchema.optional().describe("Plan start date in YYYY-MM-DD format."),
|
|
1897
|
+
planEndDate: DateSchema.optional().describe("Plan end date in YYYY-MM-DD format."),
|
|
1898
|
+
source: z.string().optional().describe("Source to update. If omitted, uses the default source.")
|
|
1899
|
+
});
|
|
1900
|
+
async function handleUpdateTaskPlanDates(input, adapters, defaultSource) {
|
|
1901
|
+
const sourceType = input.source ?? defaultSource;
|
|
1902
|
+
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
1903
|
+
const adapter = adapters.get(sourceType);
|
|
1904
|
+
if (!adapter) throw new Error(`Source "${sourceType}" is not configured. Available: ${[...adapters.keys()].join(", ")}`);
|
|
1905
|
+
return { content: [{
|
|
1906
|
+
type: "text",
|
|
1907
|
+
text: formatUpdateTaskPlanDatesResult(await adapter.updateTaskPlanDates({
|
|
1908
|
+
taskId: input.taskId,
|
|
1909
|
+
planStartDate: input.planStartDate,
|
|
1910
|
+
planEndDate: input.planEndDate
|
|
1911
|
+
}))
|
|
1912
|
+
}] };
|
|
1913
|
+
}
|
|
1914
|
+
function formatUpdateTaskPlanDatesResult(result) {
|
|
1915
|
+
const lines = [
|
|
1916
|
+
"Updated task plan dates.",
|
|
1917
|
+
"",
|
|
1918
|
+
`- **Task UUID**: ${result.taskUuid}`
|
|
1919
|
+
];
|
|
1920
|
+
if (result.planStartDate) lines.push(`- **Plan Start Date**: ${result.planStartDate}`);
|
|
1921
|
+
if (result.planEndDate) lines.push(`- **Plan End Date**: ${result.planEndDate}`);
|
|
1922
|
+
return lines.join("\n");
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1623
1925
|
//#endregion
|
|
1624
1926
|
//#region src/index.ts
|
|
1625
1927
|
/**
|
|
@@ -1745,6 +2047,32 @@ async function main() {
|
|
|
1745
2047
|
};
|
|
1746
2048
|
}
|
|
1747
2049
|
});
|
|
2050
|
+
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) => {
|
|
2051
|
+
try {
|
|
2052
|
+
return await handleAddManhour(params, adapters, config.config.defaultSource);
|
|
2053
|
+
} catch (err) {
|
|
2054
|
+
return {
|
|
2055
|
+
content: [{
|
|
2056
|
+
type: "text",
|
|
2057
|
+
text: `Error: ${err.message}`
|
|
2058
|
+
}],
|
|
2059
|
+
isError: true
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
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) => {
|
|
2064
|
+
try {
|
|
2065
|
+
return await handleUpdateTaskPlanDates(params, adapters, config.config.defaultSource);
|
|
2066
|
+
} catch (err) {
|
|
2067
|
+
return {
|
|
2068
|
+
content: [{
|
|
2069
|
+
type: "text",
|
|
2070
|
+
text: `Error: ${err.message}`
|
|
2071
|
+
}],
|
|
2072
|
+
isError: true
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
});
|
|
1748
2076
|
const transport = new StdioServerTransport();
|
|
1749
2077
|
await server.connect(transport);
|
|
1750
2078
|
}
|