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.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) {
|
|
@@ -331,6 +351,93 @@ function extractWikiPageUuidsFromText(text) {
|
|
|
331
351
|
for (const pattern of [/\/page\/([\w-]+)/g, /page=([\w-]+)/g]) for (const match of text.matchAll(pattern)) if (match[1]) uuids.add(match[1]);
|
|
332
352
|
return [...uuids];
|
|
333
353
|
}
|
|
354
|
+
function parseOnesWikiPageRoute(input) {
|
|
355
|
+
if (!input.includes("/wiki/")) return null;
|
|
356
|
+
const match = (() => {
|
|
357
|
+
try {
|
|
358
|
+
const parsed = new URL(input);
|
|
359
|
+
return `${parsed.pathname}${parsed.hash}${parsed.search}`;
|
|
360
|
+
} catch {
|
|
361
|
+
return input;
|
|
362
|
+
}
|
|
363
|
+
})().match(/\/team\/([^/?#]+)\/space\/[^/?#]+\/page\/([^/?#]+)/);
|
|
364
|
+
if (!match?.[1] || !match[2]) return null;
|
|
365
|
+
return {
|
|
366
|
+
teamUuid: decodeURIComponent(match[1]),
|
|
367
|
+
wikiUuid: decodeURIComponent(match[2])
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function isOnesWikiUrlInput(input) {
|
|
371
|
+
return input.includes("/wiki/");
|
|
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
|
+
}
|
|
334
441
|
function htmlToPlainText(html) {
|
|
335
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();
|
|
336
443
|
}
|
|
@@ -525,32 +632,35 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
525
632
|
redirect: "manual"
|
|
526
633
|
})).headers.get("location");
|
|
527
634
|
if (!authorizeLocation) throw new Error("ONES: Authorize response missing location header");
|
|
528
|
-
|
|
529
|
-
if (!
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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);
|
|
546
663
|
}
|
|
547
|
-
const callbackLocation = (await fetch(`${baseUrl}/identity/authorize/callback?id=${authRequestId}&lang=zh`, {
|
|
548
|
-
method: "GET",
|
|
549
|
-
headers: { Cookie: cookies },
|
|
550
|
-
redirect: "manual"
|
|
551
|
-
})).headers.get("location");
|
|
552
|
-
if (!callbackLocation) throw new Error("ONES: Callback response missing location header");
|
|
553
|
-
const code = new URL(callbackLocation).searchParams.get("code");
|
|
554
664
|
if (!code) throw new Error("ONES: Cannot parse authorization code from callback redirect");
|
|
555
665
|
const tokenRes = await fetch(`${baseUrl}/identity/oauth/token`, {
|
|
556
666
|
method: "POST",
|
|
@@ -620,11 +730,107 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
620
730
|
}
|
|
621
731
|
return response.json();
|
|
622
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
|
+
}
|
|
623
760
|
async fetchIssueTypes() {
|
|
624
761
|
if (this.issueTypesCache) return this.issueTypesCache;
|
|
625
762
|
this.issueTypesCache = (await this.graphql(ISSUE_TYPES_QUERY, { orderBy: { namePinyin: "ASC" } }, "issueTypes")).data?.issueTypes ?? [];
|
|
626
763
|
return this.issueTypesCache;
|
|
627
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
|
+
}
|
|
628
834
|
async searchTeamUsers(keyword) {
|
|
629
835
|
const session = await this.login();
|
|
630
836
|
const url = `${this.config.apiBase}/project/api/project/team/${session.teamUuid}/users/search`;
|
|
@@ -733,22 +939,25 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
733
939
|
* Fetch wiki page content via REST API.
|
|
734
940
|
* Endpoint: /wiki/api/wiki/team/{teamUuid}/online_page/{wikiUuid}/content
|
|
735
941
|
*/
|
|
736
|
-
async fetchWikiPageDetail(wikiUuid) {
|
|
942
|
+
async fetchWikiPageDetail(wikiUuid, teamUuid) {
|
|
737
943
|
const session = await this.login();
|
|
738
|
-
const
|
|
944
|
+
const wikiTeamUuid = teamUuid ?? session.teamUuid;
|
|
945
|
+
const url = `${this.config.apiBase}/wiki/api/wiki/team/${wikiTeamUuid}/page/${wikiUuid}/detail`;
|
|
739
946
|
const response = await fetch(url, { headers: { Authorization: `Bearer ${session.accessToken}` } });
|
|
740
947
|
if (!response.ok) return {};
|
|
741
948
|
return response.json();
|
|
742
949
|
}
|
|
743
|
-
buildWikiImageUrl(session, refUuid, source, token) {
|
|
950
|
+
buildWikiImageUrl(session, refUuid, source, token, teamUuid) {
|
|
744
951
|
const encodedRefUuid = encodeURIComponent(refUuid);
|
|
745
952
|
const encodedSource = source.split("/").map((part) => encodeURIComponent(part)).join("/");
|
|
746
953
|
const encodedToken = encodeURIComponent(token);
|
|
747
|
-
|
|
954
|
+
const wikiTeamUuid = teamUuid ?? session.teamUuid;
|
|
955
|
+
return `${this.config.apiBase}/wiki/api/wiki/editor/${wikiTeamUuid}/${encodedRefUuid}/resources/${encodedSource}?token=${encodedToken}`;
|
|
748
956
|
}
|
|
749
|
-
async fetchWikiContent(wikiUuid) {
|
|
957
|
+
async fetchWikiContent(wikiUuid, teamUuid) {
|
|
750
958
|
const session = await this.login();
|
|
751
|
-
const
|
|
959
|
+
const wikiTeamUuid = teamUuid ?? session.teamUuid;
|
|
960
|
+
const url = `${this.config.apiBase}/wiki/api/wiki/team/${wikiTeamUuid}/online_page/${wikiUuid}/content`;
|
|
752
961
|
const response = await fetch(url, { headers: { Authorization: `Bearer ${session.accessToken}` } });
|
|
753
962
|
if (!response.ok) return {
|
|
754
963
|
content: "",
|
|
@@ -762,7 +971,7 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
762
971
|
content,
|
|
763
972
|
attachments: []
|
|
764
973
|
};
|
|
765
|
-
const detail = await this.fetchWikiPageDetail(wikiUuid);
|
|
974
|
+
const detail = await this.fetchWikiPageDetail(wikiUuid, wikiTeamUuid);
|
|
766
975
|
const refUuid = typeof detail.ref_uuid === "string" ? detail.ref_uuid : "";
|
|
767
976
|
if (!refUuid) return {
|
|
768
977
|
content,
|
|
@@ -773,38 +982,46 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
773
982
|
attachments: renderContext.imageSources.map((source, index) => ({
|
|
774
983
|
id: `${wikiUuid}-image-${index + 1}`,
|
|
775
984
|
name: attachmentNameFromPath(source),
|
|
776
|
-
url: this.buildWikiImageUrl(session, refUuid, source, token),
|
|
985
|
+
url: this.buildWikiImageUrl(session, refUuid, source, token, wikiTeamUuid),
|
|
777
986
|
mimeType: mimeTypeFromFileName(source),
|
|
778
987
|
size: 0
|
|
779
988
|
}))
|
|
780
989
|
};
|
|
781
990
|
}
|
|
782
991
|
/**
|
|
783
|
-
* Fetch a single task by UUID or number (e.g. "#
|
|
992
|
+
* Fetch a single task by UUID or number (e.g. "#1001" or "1001").
|
|
784
993
|
* If a number is given, searches first to resolve the UUID.
|
|
785
994
|
*/
|
|
786
995
|
async getRequirement(params) {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
996
|
+
const wikiRoute = parseOnesWikiPageRoute(params.id);
|
|
997
|
+
if (wikiRoute) {
|
|
998
|
+
const rendered = await this.fetchWikiContent(wikiRoute.wikiUuid, wikiRoute.teamUuid);
|
|
999
|
+
return {
|
|
1000
|
+
id: wikiRoute.wikiUuid,
|
|
1001
|
+
source: "ones",
|
|
1002
|
+
title: `Wiki ${wikiRoute.wikiUuid}`,
|
|
1003
|
+
description: rendered.content,
|
|
1004
|
+
status: "open",
|
|
1005
|
+
priority: "medium",
|
|
1006
|
+
type: "feature",
|
|
1007
|
+
labels: [],
|
|
1008
|
+
reporter: "",
|
|
1009
|
+
assignee: null,
|
|
1010
|
+
createdAt: "",
|
|
1011
|
+
updatedAt: "",
|
|
1012
|
+
dueDate: null,
|
|
1013
|
+
attachments: rendered.attachments,
|
|
1014
|
+
raw: {
|
|
1015
|
+
input: params.id,
|
|
1016
|
+
teamUuid: wikiRoute.teamUuid,
|
|
1017
|
+
wikiUuid: wikiRoute.wikiUuid
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
805
1020
|
}
|
|
806
|
-
|
|
807
|
-
|
|
1021
|
+
if (isOnesWikiUrlInput(params.id)) throw new Error("ONES: Unsupported wiki page URL. Expected /wiki/#/team/{teamUuid}/space/{spaceUuid}/page/{wikiUuid}");
|
|
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`);
|
|
808
1025
|
const wikiRefs = /* @__PURE__ */ new Map();
|
|
809
1026
|
for (const wiki of task.relatedWikiPages ?? []) if (!wiki.errorMessage) wikiRefs.set(wiki.uuid, {
|
|
810
1027
|
title: wiki.title,
|
|
@@ -939,6 +1156,69 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
939
1156
|
pageSize
|
|
940
1157
|
};
|
|
941
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
|
+
}
|
|
942
1222
|
async getRelatedIssues(params) {
|
|
943
1223
|
const session = await this.login();
|
|
944
1224
|
const taskKey = params.taskId.startsWith("task-") ? params.taskId : `task-${params.taskId}`;
|
|
@@ -1286,10 +1566,46 @@ function loadConfig(startDir) {
|
|
|
1286
1566
|
};
|
|
1287
1567
|
}
|
|
1288
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
|
+
|
|
1289
1605
|
//#endregion
|
|
1290
1606
|
//#region src/tools/get-issue-detail.ts
|
|
1291
1607
|
const GetIssueDetailSchema = zod_v4.z.object({
|
|
1292
|
-
issueId: zod_v4.z.string().describe("The issue task ID or key (e.g. \"
|
|
1608
|
+
issueId: zod_v4.z.string().describe("The issue task ID or key (e.g. \"mock-issue-uuid\" or \"task-mock-issue-uuid\")"),
|
|
1293
1609
|
source: zod_v4.z.string().optional().describe("Source to fetch from. If omitted, uses the default source.")
|
|
1294
1610
|
});
|
|
1295
1611
|
/**
|
|
@@ -1364,7 +1680,7 @@ function formatIssueDetail(detail) {
|
|
|
1364
1680
|
//#endregion
|
|
1365
1681
|
//#region src/tools/get-related-issues.ts
|
|
1366
1682
|
const GetRelatedIssuesSchema = zod_v4.z.object({
|
|
1367
|
-
taskId: zod_v4.z.string().describe("The parent task ID or key (e.g. \"
|
|
1683
|
+
taskId: zod_v4.z.string().describe("The parent task ID or key (e.g. \"mock-task-uuid\" or \"task-mock-task-uuid\")"),
|
|
1368
1684
|
source: zod_v4.z.string().optional().describe("Source to fetch from. If omitted, uses the default source.")
|
|
1369
1685
|
});
|
|
1370
1686
|
async function handleGetRelatedIssues(input, adapters, defaultSource) {
|
|
@@ -1405,7 +1721,7 @@ function formatRelatedIssues(issues) {
|
|
|
1405
1721
|
//#endregion
|
|
1406
1722
|
//#region src/tools/get-requirement.ts
|
|
1407
1723
|
const GetRequirementSchema = zod_v4.z.object({
|
|
1408
|
-
id: zod_v4.z.string().describe("The requirement/issue ID"),
|
|
1724
|
+
id: zod_v4.z.string().describe("The requirement/issue ID, task number, or ONES wiki page URL"),
|
|
1409
1725
|
source: zod_v4.z.string().optional().describe("Source to fetch from. If omitted, uses the default source.")
|
|
1410
1726
|
});
|
|
1411
1727
|
async function downloadImageAsBase64(url, fallbackMimeType = "image/png") {
|
|
@@ -1600,6 +1916,40 @@ async function handleSearchRequirements(input, adapters, defaultSource) {
|
|
|
1600
1916
|
}] };
|
|
1601
1917
|
}
|
|
1602
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
|
+
|
|
1603
1953
|
//#endregion
|
|
1604
1954
|
//#region src/index.ts
|
|
1605
1955
|
/**
|
|
@@ -1725,6 +2075,32 @@ async function main() {
|
|
|
1725
2075
|
};
|
|
1726
2076
|
}
|
|
1727
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
|
+
});
|
|
1728
2104
|
const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
|
|
1729
2105
|
await server.connect(transport);
|
|
1730
2106
|
}
|