ai-dev-requirements 0.1.10 → 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 +433 -57
- package/dist/index.mjs +433 -57
- 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) {
|
|
@@ -303,6 +323,93 @@ function extractWikiPageUuidsFromText(text) {
|
|
|
303
323
|
for (const pattern of [/\/page\/([\w-]+)/g, /page=([\w-]+)/g]) for (const match of text.matchAll(pattern)) if (match[1]) uuids.add(match[1]);
|
|
304
324
|
return [...uuids];
|
|
305
325
|
}
|
|
326
|
+
function parseOnesWikiPageRoute(input) {
|
|
327
|
+
if (!input.includes("/wiki/")) return null;
|
|
328
|
+
const match = (() => {
|
|
329
|
+
try {
|
|
330
|
+
const parsed = new URL(input);
|
|
331
|
+
return `${parsed.pathname}${parsed.hash}${parsed.search}`;
|
|
332
|
+
} catch {
|
|
333
|
+
return input;
|
|
334
|
+
}
|
|
335
|
+
})().match(/\/team\/([^/?#]+)\/space\/[^/?#]+\/page\/([^/?#]+)/);
|
|
336
|
+
if (!match?.[1] || !match[2]) return null;
|
|
337
|
+
return {
|
|
338
|
+
teamUuid: decodeURIComponent(match[1]),
|
|
339
|
+
wikiUuid: decodeURIComponent(match[2])
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function isOnesWikiUrlInput(input) {
|
|
343
|
+
return input.includes("/wiki/");
|
|
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
|
+
}
|
|
306
413
|
function htmlToPlainText(html) {
|
|
307
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();
|
|
308
415
|
}
|
|
@@ -497,32 +604,35 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
497
604
|
redirect: "manual"
|
|
498
605
|
})).headers.get("location");
|
|
499
606
|
if (!authorizeLocation) throw new Error("ONES: Authorize response missing location header");
|
|
500
|
-
|
|
501
|
-
if (!
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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);
|
|
518
635
|
}
|
|
519
|
-
const callbackLocation = (await fetch(`${baseUrl}/identity/authorize/callback?id=${authRequestId}&lang=zh`, {
|
|
520
|
-
method: "GET",
|
|
521
|
-
headers: { Cookie: cookies },
|
|
522
|
-
redirect: "manual"
|
|
523
|
-
})).headers.get("location");
|
|
524
|
-
if (!callbackLocation) throw new Error("ONES: Callback response missing location header");
|
|
525
|
-
const code = new URL(callbackLocation).searchParams.get("code");
|
|
526
636
|
if (!code) throw new Error("ONES: Cannot parse authorization code from callback redirect");
|
|
527
637
|
const tokenRes = await fetch(`${baseUrl}/identity/oauth/token`, {
|
|
528
638
|
method: "POST",
|
|
@@ -592,11 +702,107 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
592
702
|
}
|
|
593
703
|
return response.json();
|
|
594
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
|
+
}
|
|
595
732
|
async fetchIssueTypes() {
|
|
596
733
|
if (this.issueTypesCache) return this.issueTypesCache;
|
|
597
734
|
this.issueTypesCache = (await this.graphql(ISSUE_TYPES_QUERY, { orderBy: { namePinyin: "ASC" } }, "issueTypes")).data?.issueTypes ?? [];
|
|
598
735
|
return this.issueTypesCache;
|
|
599
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
|
+
}
|
|
600
806
|
async searchTeamUsers(keyword) {
|
|
601
807
|
const session = await this.login();
|
|
602
808
|
const url = `${this.config.apiBase}/project/api/project/team/${session.teamUuid}/users/search`;
|
|
@@ -705,22 +911,25 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
705
911
|
* Fetch wiki page content via REST API.
|
|
706
912
|
* Endpoint: /wiki/api/wiki/team/{teamUuid}/online_page/{wikiUuid}/content
|
|
707
913
|
*/
|
|
708
|
-
async fetchWikiPageDetail(wikiUuid) {
|
|
914
|
+
async fetchWikiPageDetail(wikiUuid, teamUuid) {
|
|
709
915
|
const session = await this.login();
|
|
710
|
-
const
|
|
916
|
+
const wikiTeamUuid = teamUuid ?? session.teamUuid;
|
|
917
|
+
const url = `${this.config.apiBase}/wiki/api/wiki/team/${wikiTeamUuid}/page/${wikiUuid}/detail`;
|
|
711
918
|
const response = await fetch(url, { headers: { Authorization: `Bearer ${session.accessToken}` } });
|
|
712
919
|
if (!response.ok) return {};
|
|
713
920
|
return response.json();
|
|
714
921
|
}
|
|
715
|
-
buildWikiImageUrl(session, refUuid, source, token) {
|
|
922
|
+
buildWikiImageUrl(session, refUuid, source, token, teamUuid) {
|
|
716
923
|
const encodedRefUuid = encodeURIComponent(refUuid);
|
|
717
924
|
const encodedSource = source.split("/").map((part) => encodeURIComponent(part)).join("/");
|
|
718
925
|
const encodedToken = encodeURIComponent(token);
|
|
719
|
-
|
|
926
|
+
const wikiTeamUuid = teamUuid ?? session.teamUuid;
|
|
927
|
+
return `${this.config.apiBase}/wiki/api/wiki/editor/${wikiTeamUuid}/${encodedRefUuid}/resources/${encodedSource}?token=${encodedToken}`;
|
|
720
928
|
}
|
|
721
|
-
async fetchWikiContent(wikiUuid) {
|
|
929
|
+
async fetchWikiContent(wikiUuid, teamUuid) {
|
|
722
930
|
const session = await this.login();
|
|
723
|
-
const
|
|
931
|
+
const wikiTeamUuid = teamUuid ?? session.teamUuid;
|
|
932
|
+
const url = `${this.config.apiBase}/wiki/api/wiki/team/${wikiTeamUuid}/online_page/${wikiUuid}/content`;
|
|
724
933
|
const response = await fetch(url, { headers: { Authorization: `Bearer ${session.accessToken}` } });
|
|
725
934
|
if (!response.ok) return {
|
|
726
935
|
content: "",
|
|
@@ -734,7 +943,7 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
734
943
|
content,
|
|
735
944
|
attachments: []
|
|
736
945
|
};
|
|
737
|
-
const detail = await this.fetchWikiPageDetail(wikiUuid);
|
|
946
|
+
const detail = await this.fetchWikiPageDetail(wikiUuid, wikiTeamUuid);
|
|
738
947
|
const refUuid = typeof detail.ref_uuid === "string" ? detail.ref_uuid : "";
|
|
739
948
|
if (!refUuid) return {
|
|
740
949
|
content,
|
|
@@ -745,38 +954,46 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
745
954
|
attachments: renderContext.imageSources.map((source, index) => ({
|
|
746
955
|
id: `${wikiUuid}-image-${index + 1}`,
|
|
747
956
|
name: attachmentNameFromPath(source),
|
|
748
|
-
url: this.buildWikiImageUrl(session, refUuid, source, token),
|
|
957
|
+
url: this.buildWikiImageUrl(session, refUuid, source, token, wikiTeamUuid),
|
|
749
958
|
mimeType: mimeTypeFromFileName(source),
|
|
750
959
|
size: 0
|
|
751
960
|
}))
|
|
752
961
|
};
|
|
753
962
|
}
|
|
754
963
|
/**
|
|
755
|
-
* Fetch a single task by UUID or number (e.g. "#
|
|
964
|
+
* Fetch a single task by UUID or number (e.g. "#1001" or "1001").
|
|
756
965
|
* If a number is given, searches first to resolve the UUID.
|
|
757
966
|
*/
|
|
758
967
|
async getRequirement(params) {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
968
|
+
const wikiRoute = parseOnesWikiPageRoute(params.id);
|
|
969
|
+
if (wikiRoute) {
|
|
970
|
+
const rendered = await this.fetchWikiContent(wikiRoute.wikiUuid, wikiRoute.teamUuid);
|
|
971
|
+
return {
|
|
972
|
+
id: wikiRoute.wikiUuid,
|
|
973
|
+
source: "ones",
|
|
974
|
+
title: `Wiki ${wikiRoute.wikiUuid}`,
|
|
975
|
+
description: rendered.content,
|
|
976
|
+
status: "open",
|
|
977
|
+
priority: "medium",
|
|
978
|
+
type: "feature",
|
|
979
|
+
labels: [],
|
|
980
|
+
reporter: "",
|
|
981
|
+
assignee: null,
|
|
982
|
+
createdAt: "",
|
|
983
|
+
updatedAt: "",
|
|
984
|
+
dueDate: null,
|
|
985
|
+
attachments: rendered.attachments,
|
|
986
|
+
raw: {
|
|
987
|
+
input: params.id,
|
|
988
|
+
teamUuid: wikiRoute.teamUuid,
|
|
989
|
+
wikiUuid: wikiRoute.wikiUuid
|
|
990
|
+
}
|
|
991
|
+
};
|
|
777
992
|
}
|
|
778
|
-
|
|
779
|
-
|
|
993
|
+
if (isOnesWikiUrlInput(params.id)) throw new Error("ONES: Unsupported wiki page URL. Expected /wiki/#/team/{teamUuid}/space/{spaceUuid}/page/{wikiUuid}");
|
|
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`);
|
|
780
997
|
const wikiRefs = /* @__PURE__ */ new Map();
|
|
781
998
|
for (const wiki of task.relatedWikiPages ?? []) if (!wiki.errorMessage) wikiRefs.set(wiki.uuid, {
|
|
782
999
|
title: wiki.title,
|
|
@@ -911,6 +1128,69 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
911
1128
|
pageSize
|
|
912
1129
|
};
|
|
913
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
|
+
}
|
|
914
1194
|
async getRelatedIssues(params) {
|
|
915
1195
|
const session = await this.login();
|
|
916
1196
|
const taskKey = params.taskId.startsWith("task-") ? params.taskId : `task-${params.taskId}`;
|
|
@@ -1258,10 +1538,46 @@ function loadConfig(startDir) {
|
|
|
1258
1538
|
};
|
|
1259
1539
|
}
|
|
1260
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
|
+
|
|
1261
1577
|
//#endregion
|
|
1262
1578
|
//#region src/tools/get-issue-detail.ts
|
|
1263
1579
|
const GetIssueDetailSchema = z.object({
|
|
1264
|
-
issueId: z.string().describe("The issue task ID or key (e.g. \"
|
|
1580
|
+
issueId: z.string().describe("The issue task ID or key (e.g. \"mock-issue-uuid\" or \"task-mock-issue-uuid\")"),
|
|
1265
1581
|
source: z.string().optional().describe("Source to fetch from. If omitted, uses the default source.")
|
|
1266
1582
|
});
|
|
1267
1583
|
/**
|
|
@@ -1336,7 +1652,7 @@ function formatIssueDetail(detail) {
|
|
|
1336
1652
|
//#endregion
|
|
1337
1653
|
//#region src/tools/get-related-issues.ts
|
|
1338
1654
|
const GetRelatedIssuesSchema = z.object({
|
|
1339
|
-
taskId: z.string().describe("The parent task ID or key (e.g. \"
|
|
1655
|
+
taskId: z.string().describe("The parent task ID or key (e.g. \"mock-task-uuid\" or \"task-mock-task-uuid\")"),
|
|
1340
1656
|
source: z.string().optional().describe("Source to fetch from. If omitted, uses the default source.")
|
|
1341
1657
|
});
|
|
1342
1658
|
async function handleGetRelatedIssues(input, adapters, defaultSource) {
|
|
@@ -1377,7 +1693,7 @@ function formatRelatedIssues(issues) {
|
|
|
1377
1693
|
//#endregion
|
|
1378
1694
|
//#region src/tools/get-requirement.ts
|
|
1379
1695
|
const GetRequirementSchema = z.object({
|
|
1380
|
-
id: z.string().describe("The requirement/issue ID"),
|
|
1696
|
+
id: z.string().describe("The requirement/issue ID, task number, or ONES wiki page URL"),
|
|
1381
1697
|
source: z.string().optional().describe("Source to fetch from. If omitted, uses the default source.")
|
|
1382
1698
|
});
|
|
1383
1699
|
async function downloadImageAsBase64(url, fallbackMimeType = "image/png") {
|
|
@@ -1572,6 +1888,40 @@ async function handleSearchRequirements(input, adapters, defaultSource) {
|
|
|
1572
1888
|
}] };
|
|
1573
1889
|
}
|
|
1574
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
|
+
|
|
1575
1925
|
//#endregion
|
|
1576
1926
|
//#region src/index.ts
|
|
1577
1927
|
/**
|
|
@@ -1697,6 +2047,32 @@ async function main() {
|
|
|
1697
2047
|
};
|
|
1698
2048
|
}
|
|
1699
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
|
+
});
|
|
1700
2076
|
const transport = new StdioServerTransport();
|
|
1701
2077
|
await server.connect(transport);
|
|
1702
2078
|
}
|