ai-dev-requirements 0.1.11 → 0.1.13
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 +591 -90
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +585 -78
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -11
package/dist/index.cjs
CHANGED
|
@@ -7,16 +7,12 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
9
|
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
}
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
20
16
|
}
|
|
21
17
|
return to;
|
|
22
18
|
};
|
|
@@ -24,16 +20,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
20
|
value: mod,
|
|
25
21
|
enumerable: true
|
|
26
22
|
}) : target, mod));
|
|
27
|
-
|
|
28
23
|
//#endregion
|
|
29
24
|
let node_fs = require("node:fs");
|
|
30
25
|
let node_path = require("node:path");
|
|
31
26
|
let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
32
27
|
let _modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
33
28
|
let node_crypto = require("node:crypto");
|
|
34
|
-
node_crypto = __toESM(node_crypto);
|
|
29
|
+
node_crypto = __toESM(node_crypto, 1);
|
|
35
30
|
let zod_v4 = require("zod/v4");
|
|
36
|
-
|
|
37
31
|
//#region src/utils/map-status.ts
|
|
38
32
|
const ONES_STATUS_MAP = {
|
|
39
33
|
to_do: "open",
|
|
@@ -69,7 +63,6 @@ function mapOnesPriority(priority) {
|
|
|
69
63
|
function mapOnesType(type) {
|
|
70
64
|
return ONES_TYPE_MAP[type.toLowerCase()] ?? "task";
|
|
71
65
|
}
|
|
72
|
-
|
|
73
66
|
//#endregion
|
|
74
67
|
//#region src/adapters/base.ts
|
|
75
68
|
/**
|
|
@@ -86,7 +79,6 @@ var BaseAdapter = class {
|
|
|
86
79
|
this.resolvedAuth = resolvedAuth;
|
|
87
80
|
}
|
|
88
81
|
};
|
|
89
|
-
|
|
90
82
|
//#endregion
|
|
91
83
|
//#region src/adapters/ones.ts
|
|
92
84
|
const TASK_DETAIL_QUERY = `
|
|
@@ -120,6 +112,26 @@ const TASK_DETAIL_QUERY = `
|
|
|
120
112
|
}
|
|
121
113
|
}
|
|
122
114
|
`;
|
|
115
|
+
const RELATED_ACTIVITIES_QUERY = `
|
|
116
|
+
query Task($key: Key) {
|
|
117
|
+
task(key: $key) {
|
|
118
|
+
key
|
|
119
|
+
...RelatedActivities_task1
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fragment RelatedActivities_task1 on Task {
|
|
124
|
+
relatedActivities {
|
|
125
|
+
uuid
|
|
126
|
+
name
|
|
127
|
+
projectUUID
|
|
128
|
+
project_uuid: projectUUID
|
|
129
|
+
relatedChild
|
|
130
|
+
related_child_uuid: relatedChild
|
|
131
|
+
}
|
|
132
|
+
relatedActivitiesCount
|
|
133
|
+
}
|
|
134
|
+
`;
|
|
123
135
|
const SEARCH_TASKS_QUERY = `
|
|
124
136
|
query GROUP_TASK_DATA($groupBy: GroupBy, $groupOrderBy: OrderBy, $orderBy: OrderBy, $filterGroup: [Filter!], $search: Search, $pagination: Pagination, $limit: Int) {
|
|
125
137
|
buckets(groupBy: $groupBy, orderBy: $groupOrderBy, pagination: $pagination, filter: $search) {
|
|
@@ -144,6 +156,26 @@ const ISSUE_TYPES_QUERY = `
|
|
|
144
156
|
}
|
|
145
157
|
}
|
|
146
158
|
`;
|
|
159
|
+
const PROJECTS_QUERY = `
|
|
160
|
+
query Projects($groupBy: GroupBy, $orderBy: OrderBy, $pagination: Pagination, $projectOrderBy: OrderBy, $projectFilterGroup: [Filter!]) {
|
|
161
|
+
buckets(groupBy: $groupBy, orderBy: $orderBy, pagination: $pagination) {
|
|
162
|
+
key
|
|
163
|
+
projects(limit: 10000, orderBy: $projectOrderBy, filterGroup: $projectFilterGroup) {
|
|
164
|
+
key
|
|
165
|
+
uuid
|
|
166
|
+
name
|
|
167
|
+
identifier
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
const ADD_MANHOUR_MUTATION = `
|
|
173
|
+
mutation AddManhour {
|
|
174
|
+
addManhour(mode: $mode, owner: $owner, task: $task, type: $type, start_time: $start_time, hours: $hours, description: $description, customData: $customData) {
|
|
175
|
+
key
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
`;
|
|
147
179
|
const TASK_BY_NUMBER_QUERY = SEARCH_TASKS_QUERY;
|
|
148
180
|
const RELATED_TASKS_QUERY = `
|
|
149
181
|
query Task($key: Key) {
|
|
@@ -332,7 +364,7 @@ function extractWikiPageUuidsFromText(text) {
|
|
|
332
364
|
return [...uuids];
|
|
333
365
|
}
|
|
334
366
|
function parseOnesWikiPageRoute(input) {
|
|
335
|
-
if (!input
|
|
367
|
+
if (!isOnesWikiUrlInput(input)) return null;
|
|
336
368
|
const match = (() => {
|
|
337
369
|
try {
|
|
338
370
|
const parsed = new URL(input);
|
|
@@ -340,7 +372,7 @@ function parseOnesWikiPageRoute(input) {
|
|
|
340
372
|
} catch {
|
|
341
373
|
return input;
|
|
342
374
|
}
|
|
343
|
-
})().match(/\/team\/([^/?#]+)\/space\/[^/?#]+\/page\/([^/?#]+)/);
|
|
375
|
+
})().match(/\/team\/([^/?#]+)\/(?:space\/[^/?#]+\/)?page\/([^/?#]+)/);
|
|
344
376
|
if (!match?.[1] || !match[2]) return null;
|
|
345
377
|
return {
|
|
346
378
|
teamUuid: decodeURIComponent(match[1]),
|
|
@@ -348,7 +380,75 @@ function parseOnesWikiPageRoute(input) {
|
|
|
348
380
|
};
|
|
349
381
|
}
|
|
350
382
|
function isOnesWikiUrlInput(input) {
|
|
351
|
-
return
|
|
383
|
+
return /\/wiki(?:\/|(?=[#?]|$))/.test(input);
|
|
384
|
+
}
|
|
385
|
+
function parseAuthorizeRequestId(location) {
|
|
386
|
+
try {
|
|
387
|
+
const parsed = new URL(location);
|
|
388
|
+
return parsed.searchParams.get("auth_request_id") ?? parsed.searchParams.get("id");
|
|
389
|
+
} catch {
|
|
390
|
+
const match = location.match(/[?&](?:auth_request_id|id)=([^&#]+)/);
|
|
391
|
+
return match?.[1] ? decodeURIComponent(match[1]) : null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function parseAuthorizationCode(location) {
|
|
395
|
+
try {
|
|
396
|
+
return new URL(location).searchParams.get("code");
|
|
397
|
+
} catch {
|
|
398
|
+
const match = location.match(/[?&]code=([^&#]+)/);
|
|
399
|
+
return match?.[1] ? decodeURIComponent(match[1]) : null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function parseDisplayId(input) {
|
|
403
|
+
const match = input.trim().match(/^([a-z]\w*)-(\d+)$/i);
|
|
404
|
+
if (!match?.[1] || !match[2]) return null;
|
|
405
|
+
return {
|
|
406
|
+
identifier: match[1],
|
|
407
|
+
number: Number.parseInt(match[2], 10)
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function isValidOnesDate(value) {
|
|
411
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
412
|
+
const [yearText, monthText, dayText] = value.split("-");
|
|
413
|
+
const year = Number.parseInt(yearText, 10);
|
|
414
|
+
const month = Number.parseInt(monthText, 10);
|
|
415
|
+
const day = Number.parseInt(dayText, 10);
|
|
416
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
417
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
418
|
+
}
|
|
419
|
+
function toOnesHours(hours) {
|
|
420
|
+
if (!Number.isFinite(hours) || hours <= 0) throw new Error("ONES: hours must be a positive number");
|
|
421
|
+
return Math.round(hours * 1e5);
|
|
422
|
+
}
|
|
423
|
+
function getTodayStartUnixSeconds() {
|
|
424
|
+
const now = /* @__PURE__ */ new Date();
|
|
425
|
+
const localStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
426
|
+
return Math.floor(localStart.getTime() / 1e3);
|
|
427
|
+
}
|
|
428
|
+
function toLocalDateString(date) {
|
|
429
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
430
|
+
}
|
|
431
|
+
function getLocalStartUnixSeconds(year, month, day) {
|
|
432
|
+
return Math.floor(new Date(year, month - 1, day).getTime() / 1e3);
|
|
433
|
+
}
|
|
434
|
+
function parseManhourDate(input) {
|
|
435
|
+
const value = input?.trim();
|
|
436
|
+
if (!value) return {
|
|
437
|
+
date: null,
|
|
438
|
+
startTime: getTodayStartUnixSeconds()
|
|
439
|
+
};
|
|
440
|
+
const fullDateMatch = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
441
|
+
const dayOnlyMatch = value.match(/^(\d{1,2})号?$/);
|
|
442
|
+
const now = /* @__PURE__ */ new Date();
|
|
443
|
+
const year = fullDateMatch ? Number.parseInt(fullDateMatch[1], 10) : now.getFullYear();
|
|
444
|
+
const month = fullDateMatch ? Number.parseInt(fullDateMatch[2], 10) : now.getMonth() + 1;
|
|
445
|
+
const day = fullDateMatch ? Number.parseInt(fullDateMatch[3], 10) : dayOnlyMatch ? Number.parseInt(dayOnlyMatch[1], 10) : NaN;
|
|
446
|
+
const parsed = new Date(year, month - 1, day);
|
|
447
|
+
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");
|
|
448
|
+
return {
|
|
449
|
+
date: toLocalDateString(parsed),
|
|
450
|
+
startTime: getLocalStartUnixSeconds(year, month, day)
|
|
451
|
+
};
|
|
352
452
|
}
|
|
353
453
|
function htmlToPlainText(html) {
|
|
354
454
|
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();
|
|
@@ -404,22 +504,126 @@ function renderWikiEmbed(block, context) {
|
|
|
404
504
|
function escapeWikiTableCell(value) {
|
|
405
505
|
return value.replace(/\|/g, "\\|").replace(/[ \t]*\n+[ \t]*/g, " ").trim();
|
|
406
506
|
}
|
|
507
|
+
function escapeWikiHtml(value) {
|
|
508
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
509
|
+
}
|
|
510
|
+
function parseWikiTableSpan(value) {
|
|
511
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 1;
|
|
512
|
+
return Math.max(Math.trunc(value), 1);
|
|
513
|
+
}
|
|
514
|
+
function buildWikiTableLayout(block) {
|
|
515
|
+
const columnCount = typeof block.cols === "number" && block.cols > 0 ? Math.trunc(block.cols) : 0;
|
|
516
|
+
const children = Array.isArray(block.children) ? block.children.filter((child) => typeof child === "string") : [];
|
|
517
|
+
if (!columnCount || !children.length) return null;
|
|
518
|
+
const hasDeclaredRows = typeof block.rows === "number" && block.rows > 0;
|
|
519
|
+
const initialRowCount = hasDeclaredRows ? Math.trunc(block.rows) : Math.max(Math.ceil(children.length / columnCount), 1);
|
|
520
|
+
const occupied = [];
|
|
521
|
+
const rows = [];
|
|
522
|
+
const ensureRowCount = (count) => {
|
|
523
|
+
while (occupied.length < count) {
|
|
524
|
+
occupied.push(Array.from({ length: columnCount }).fill(false));
|
|
525
|
+
rows.push([]);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
ensureRowCount(initialRowCount);
|
|
529
|
+
let cursor = 0;
|
|
530
|
+
let hasMergedCells = false;
|
|
531
|
+
for (const childId of children) {
|
|
532
|
+
while (true) {
|
|
533
|
+
const row = Math.floor(cursor / columnCount);
|
|
534
|
+
const column = cursor % columnCount;
|
|
535
|
+
ensureRowCount(row + 1);
|
|
536
|
+
if (!occupied[row][column]) break;
|
|
537
|
+
cursor += 1;
|
|
538
|
+
}
|
|
539
|
+
const row = Math.floor(cursor / columnCount);
|
|
540
|
+
const column = cursor % columnCount;
|
|
541
|
+
const requestedRowSpan = parseWikiTableSpan(block[`${childId}_rowSpan`]);
|
|
542
|
+
if (!hasDeclaredRows) ensureRowCount(row + requestedRowSpan);
|
|
543
|
+
const rowSpan = Math.min(requestedRowSpan, occupied.length - row);
|
|
544
|
+
const colSpan = Math.min(parseWikiTableSpan(block[`${childId}_colSpan`]), columnCount - column);
|
|
545
|
+
hasMergedCells ||= rowSpan > 1 || colSpan > 1;
|
|
546
|
+
rows[row].push({
|
|
547
|
+
childId,
|
|
548
|
+
row,
|
|
549
|
+
column,
|
|
550
|
+
rowSpan,
|
|
551
|
+
colSpan
|
|
552
|
+
});
|
|
553
|
+
for (let rowOffset = 0; rowOffset < rowSpan; rowOffset += 1) for (let columnOffset = 0; columnOffset < colSpan; columnOffset += 1) occupied[row + rowOffset][column + columnOffset] = true;
|
|
554
|
+
cursor += 1;
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
columnCount,
|
|
558
|
+
rows,
|
|
559
|
+
hasMergedCells
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function wikiCellContainsTable(value) {
|
|
563
|
+
return asWikiBlocks(value).some((block) => block.type === "table");
|
|
564
|
+
}
|
|
565
|
+
function renderWikiTextRunsHtml(value) {
|
|
566
|
+
if (!Array.isArray(value)) return "";
|
|
567
|
+
return value.map((run) => {
|
|
568
|
+
if (!isRecord(run)) return "";
|
|
569
|
+
const attributes = isRecord(run.attributes) ? run.attributes : {};
|
|
570
|
+
let content = escapeWikiHtml(typeof run.insert === "string" ? run.insert.replace(/\u00A0/g, " ") : "").replace(/\n/g, "<br>");
|
|
571
|
+
if (attributes.code) content = `<code>${content}</code>`;
|
|
572
|
+
if (attributes.bold) content = `<strong>${content}</strong>`;
|
|
573
|
+
if (attributes.italic) content = `<em>${content}</em>`;
|
|
574
|
+
if (attributes.underline) content = `<u>${content}</u>`;
|
|
575
|
+
if (attributes.strike) content = `<s>${content}</s>`;
|
|
576
|
+
const link = typeof attributes.link === "string" ? attributes.link : "";
|
|
577
|
+
return link ? `<a href="${escapeWikiHtml(link)}">${content}</a>` : content;
|
|
578
|
+
}).join("");
|
|
579
|
+
}
|
|
580
|
+
function renderWikiCellHtml(value, document, context) {
|
|
581
|
+
return asWikiBlocks(value).map((block) => renderWikiBlockHtml(block, document, context)).filter(Boolean).join("");
|
|
582
|
+
}
|
|
583
|
+
function renderWikiBlockHtml(block, document, context) {
|
|
584
|
+
if (block.type === "table") {
|
|
585
|
+
const layout = buildWikiTableLayout(block);
|
|
586
|
+
return layout ? renderWikiTableHtml(layout, document, context) : "";
|
|
587
|
+
}
|
|
588
|
+
if (block.type === "embed") return `<p>${escapeWikiHtml(renderWikiEmbed(block, context))}</p>`;
|
|
589
|
+
const text = renderWikiTextRunsHtml(block.text);
|
|
590
|
+
if (!text) return "";
|
|
591
|
+
if (block.type === "list") {
|
|
592
|
+
const tag = block.ordered ? "ol" : "ul";
|
|
593
|
+
return `<${tag}><li>${text}</li></${tag}>`;
|
|
594
|
+
}
|
|
595
|
+
if (block.heading) {
|
|
596
|
+
const level = Math.min(Math.max(Math.trunc(block.heading), 1), 6);
|
|
597
|
+
return `<h${level}>${text}</h${level}>`;
|
|
598
|
+
}
|
|
599
|
+
return `<p>${text}</p>`;
|
|
600
|
+
}
|
|
601
|
+
function renderWikiTableHtml(layout, document, context) {
|
|
602
|
+
return `<table>\n<tbody>\n${layout.rows.map((row) => {
|
|
603
|
+
return `<tr>\n${row.map((cell) => {
|
|
604
|
+
const attributes = [cell.rowSpan > 1 ? `rowspan="${cell.rowSpan}"` : "", cell.colSpan > 1 ? `colspan="${cell.colSpan}"` : ""].filter(Boolean);
|
|
605
|
+
const content = renderWikiCellHtml(document[cell.childId], document, context);
|
|
606
|
+
return `<td${attributes.length ? ` ${attributes.join(" ")}` : ""}>${content}</td>`;
|
|
607
|
+
}).join("\n")}\n</tr>`;
|
|
608
|
+
}).join("\n")}\n</tbody>\n</table>`;
|
|
609
|
+
}
|
|
407
610
|
function renderWikiCell(value, document, context) {
|
|
408
611
|
const blocks = asWikiBlocks(value);
|
|
409
612
|
if (!blocks.length) return "";
|
|
410
613
|
return blocks.map((block) => renderWikiBlock(block, document, context)).filter(Boolean).join(" ").replace(/[ \t]*\n+[ \t]*/g, " ").trim();
|
|
411
614
|
}
|
|
412
615
|
function renderWikiTable(block, document, context) {
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
616
|
+
const layout = buildWikiTableLayout(block);
|
|
617
|
+
if (!layout) return "";
|
|
618
|
+
const hasNestedTable = layout.rows.some((row) => row.some((cell) => wikiCellContainsTable(document[cell.childId])));
|
|
619
|
+
if (layout.hasMergedCells || hasNestedTable) return renderWikiTableHtml(layout, document, context);
|
|
416
620
|
const rows = [];
|
|
417
|
-
for (
|
|
418
|
-
const cells =
|
|
419
|
-
|
|
621
|
+
for (const row of layout.rows) {
|
|
622
|
+
const cells = Array.from({ length: layout.columnCount }).fill("");
|
|
623
|
+
for (const cell of row) cells[cell.column] = escapeWikiTableCell(renderWikiCell(document[cell.childId], document, context));
|
|
420
624
|
rows.push(`| ${cells.join(" | ")} |`);
|
|
421
625
|
}
|
|
422
|
-
if (rows.length > 1) rows.splice(1, 0, `| ${Array.from({ length:
|
|
626
|
+
if (rows.length > 1) rows.splice(1, 0, `| ${Array.from({ length: layout.columnCount }).fill("---").join(" | ")} |`);
|
|
423
627
|
return rows.join("\n");
|
|
424
628
|
}
|
|
425
629
|
function renderWikiBlock(block, document, context) {
|
|
@@ -544,32 +748,35 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
544
748
|
redirect: "manual"
|
|
545
749
|
})).headers.get("location");
|
|
546
750
|
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
|
-
|
|
751
|
+
let code = parseAuthorizationCode(authorizeLocation);
|
|
752
|
+
if (!code) {
|
|
753
|
+
const authRequestId = parseAuthorizeRequestId(authorizeLocation);
|
|
754
|
+
if (!authRequestId) throw new Error("ONES: Cannot parse auth_request_id from authorize redirect");
|
|
755
|
+
const finalizeRes = await fetch(`${baseUrl}/identity/api/auth_request/finalize`, {
|
|
756
|
+
method: "POST",
|
|
757
|
+
headers: {
|
|
758
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
759
|
+
"Cookie": cookies
|
|
760
|
+
},
|
|
761
|
+
body: JSON.stringify({
|
|
762
|
+
auth_request_id: authRequestId,
|
|
763
|
+
region_uuid: orgUser.region_uuid,
|
|
764
|
+
org_uuid: orgUser.org_uuid,
|
|
765
|
+
org_user_uuid: orgUser.org_user.org_user_uuid
|
|
766
|
+
})
|
|
767
|
+
});
|
|
768
|
+
if (!finalizeRes.ok) {
|
|
769
|
+
const text = await finalizeRes.text().catch(() => "");
|
|
770
|
+
throw new Error(`ONES: Finalize failed: ${finalizeRes.status} ${text}`);
|
|
771
|
+
}
|
|
772
|
+
const callbackLocation = (await fetch(`${baseUrl}/identity/authorize/callback?id=${authRequestId}&lang=zh`, {
|
|
773
|
+
method: "GET",
|
|
774
|
+
headers: { Cookie: cookies },
|
|
775
|
+
redirect: "manual"
|
|
776
|
+
})).headers.get("location");
|
|
777
|
+
if (!callbackLocation) throw new Error("ONES: Callback response missing location header");
|
|
778
|
+
code = parseAuthorizationCode(callbackLocation);
|
|
565
779
|
}
|
|
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
780
|
if (!code) throw new Error("ONES: Cannot parse authorization code from callback redirect");
|
|
574
781
|
const tokenRes = await fetch(`${baseUrl}/identity/oauth/token`, {
|
|
575
782
|
method: "POST",
|
|
@@ -639,11 +846,136 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
639
846
|
}
|
|
640
847
|
return response.json();
|
|
641
848
|
}
|
|
849
|
+
async onesql(query, variables, workItemType) {
|
|
850
|
+
const session = await this.login();
|
|
851
|
+
const url = `${this.config.apiBase}/project/api/ones-project/team/${session.teamUuid}/workitems/onesql`;
|
|
852
|
+
const response = await fetch(url, {
|
|
853
|
+
method: "POST",
|
|
854
|
+
headers: {
|
|
855
|
+
"Authorization": `Bearer ${session.accessToken}`,
|
|
856
|
+
"Content-Type": "application/json"
|
|
857
|
+
},
|
|
858
|
+
body: JSON.stringify({
|
|
859
|
+
query,
|
|
860
|
+
variables: [
|
|
861
|
+
variables,
|
|
862
|
+
workItemType,
|
|
863
|
+
null,
|
|
864
|
+
null
|
|
865
|
+
]
|
|
866
|
+
})
|
|
867
|
+
});
|
|
868
|
+
if (!response.ok) {
|
|
869
|
+
const text = await response.text().catch(() => "");
|
|
870
|
+
throw new Error(`ONES OneSQL error: ${response.status} ${text}`);
|
|
871
|
+
}
|
|
872
|
+
return response.json();
|
|
873
|
+
}
|
|
874
|
+
async fetchRelatedActivities(taskKey) {
|
|
875
|
+
return (await this.onesql(RELATED_ACTIVITIES_QUERY, { key: taskKey }, "Task")).data?.task?.relatedActivities ?? [];
|
|
876
|
+
}
|
|
877
|
+
async searchTaskByNumber(taskNumber) {
|
|
878
|
+
const session = await this.login();
|
|
879
|
+
const url = `${this.config.apiBase}/project/api/project/team/${session.teamUuid}/search?q=${encodeURIComponent(String(taskNumber))}&start=0&limit=10&types=task`;
|
|
880
|
+
const response = await fetch(url, { headers: { Authorization: `Bearer ${session.accessToken}` } });
|
|
881
|
+
if (!response.ok) return null;
|
|
882
|
+
const found = ((await response.json()).datas?.task ?? []).map((item) => item.fields).find((fields) => fields?.uuid && fields.number === taskNumber);
|
|
883
|
+
if (!found?.uuid) return null;
|
|
884
|
+
return {
|
|
885
|
+
key: `task-${found.uuid}`,
|
|
886
|
+
uuid: found.uuid,
|
|
887
|
+
number: found.number ?? taskNumber,
|
|
888
|
+
name: found.summary ?? "",
|
|
889
|
+
status: {
|
|
890
|
+
uuid: "",
|
|
891
|
+
name: "",
|
|
892
|
+
category: void 0
|
|
893
|
+
},
|
|
894
|
+
issueType: found.issue_type_uuid || found.issue_type_name ? {
|
|
895
|
+
uuid: found.issue_type_uuid ?? "",
|
|
896
|
+
name: found.issue_type_name ?? ""
|
|
897
|
+
} : void 0,
|
|
898
|
+
project: found.project_uuid || found.project_name ? {
|
|
899
|
+
uuid: found.project_uuid ?? "",
|
|
900
|
+
name: found.project_name ?? ""
|
|
901
|
+
} : void 0
|
|
902
|
+
};
|
|
903
|
+
}
|
|
642
904
|
async fetchIssueTypes() {
|
|
643
905
|
if (this.issueTypesCache) return this.issueTypesCache;
|
|
644
|
-
|
|
906
|
+
const data = await this.graphql(ISSUE_TYPES_QUERY, { orderBy: { namePinyin: "ASC" } }, "issueTypes");
|
|
907
|
+
this.issueTypesCache = data.data?.issueTypes ?? [];
|
|
645
908
|
return this.issueTypesCache;
|
|
646
909
|
}
|
|
910
|
+
async fetchProjects() {
|
|
911
|
+
return (await this.graphql(PROJECTS_QUERY, {
|
|
912
|
+
projectOrderBy: {
|
|
913
|
+
isPin: "DESC",
|
|
914
|
+
namePinyin: "ASC",
|
|
915
|
+
createTime: "DESC"
|
|
916
|
+
},
|
|
917
|
+
projectFilterGroup: [{
|
|
918
|
+
visibleInProject_equal: true,
|
|
919
|
+
isArchive_equal: false
|
|
920
|
+
}],
|
|
921
|
+
groupBy: { projects: {} },
|
|
922
|
+
orderBy: null,
|
|
923
|
+
pagination: {
|
|
924
|
+
limit: 50,
|
|
925
|
+
after: "",
|
|
926
|
+
preciseCount: true
|
|
927
|
+
}
|
|
928
|
+
}, "projects-group-list-for-project-view")).data?.buckets?.flatMap((bucket) => bucket.projects ?? []) ?? [];
|
|
929
|
+
}
|
|
930
|
+
async findTaskByNumber(taskNumber, projectUuid) {
|
|
931
|
+
const filter = { number_in: [taskNumber] };
|
|
932
|
+
if (projectUuid) filter.project_in = [projectUuid];
|
|
933
|
+
const found = ((await this.graphql(TASK_BY_NUMBER_QUERY, {
|
|
934
|
+
groupBy: { tasks: {} },
|
|
935
|
+
groupOrderBy: null,
|
|
936
|
+
orderBy: { createTime: "DESC" },
|
|
937
|
+
filterGroup: [filter],
|
|
938
|
+
search: null,
|
|
939
|
+
pagination: {
|
|
940
|
+
limit: 10,
|
|
941
|
+
preciseCount: false
|
|
942
|
+
},
|
|
943
|
+
limit: 10
|
|
944
|
+
}, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? []).find((task) => task.number === taskNumber && (!projectUuid || task.project?.uuid === projectUuid));
|
|
945
|
+
if (found) return found;
|
|
946
|
+
if (projectUuid) return null;
|
|
947
|
+
return this.searchTaskByNumber(taskNumber);
|
|
948
|
+
}
|
|
949
|
+
async resolveTaskRef(input) {
|
|
950
|
+
const taskId = input.trim();
|
|
951
|
+
if (!taskId) throw new Error("ONES: taskId is required");
|
|
952
|
+
const numMatch = taskId.match(/^#?(\d+)$/);
|
|
953
|
+
if (numMatch) {
|
|
954
|
+
const taskNumber = Number.parseInt(numMatch[1], 10);
|
|
955
|
+
const found = await this.findTaskByNumber(taskNumber);
|
|
956
|
+
if (!found) throw new Error(`ONES: Task #${taskNumber} not found in current team`);
|
|
957
|
+
return {
|
|
958
|
+
key: found.key ?? `task-${found.uuid}`,
|
|
959
|
+
uuid: found.uuid
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
const displayId = parseDisplayId(taskId);
|
|
963
|
+
if (displayId) {
|
|
964
|
+
const project = (await this.fetchProjects()).find((item) => item.identifier?.toLowerCase() === displayId.identifier.toLowerCase());
|
|
965
|
+
if (!project) throw new Error(`ONES: Project identifier "${displayId.identifier}" not found in current team`);
|
|
966
|
+
const found = await this.findTaskByNumber(displayId.number, project.uuid);
|
|
967
|
+
if (!found) throw new Error(`ONES: Task "${taskId}" not found in current team`);
|
|
968
|
+
return {
|
|
969
|
+
key: found.key ?? `task-${found.uuid}`,
|
|
970
|
+
uuid: found.uuid
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
const key = taskId.startsWith("task-") ? taskId : `task-${taskId}`;
|
|
974
|
+
return {
|
|
975
|
+
key,
|
|
976
|
+
uuid: key.slice(5)
|
|
977
|
+
};
|
|
978
|
+
}
|
|
647
979
|
async searchTeamUsers(keyword) {
|
|
648
980
|
const session = await this.login();
|
|
649
981
|
const url = `${this.config.apiBase}/project/api/project/team/${session.teamUuid}/users/search`;
|
|
@@ -832,27 +1164,11 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
832
1164
|
};
|
|
833
1165
|
}
|
|
834
1166
|
if (isOnesWikiUrlInput(params.id)) throw new Error("ONES: Unsupported wiki page URL. Expected /wiki/#/team/{teamUuid}/space/{spaceUuid}/page/{wikiUuid}");
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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`);
|
|
1167
|
+
const taskRef = await this.resolveTaskRef(params.id);
|
|
1168
|
+
const shouldFetchRelatedActivities = parseDisplayId(params.id.trim()) !== null;
|
|
1169
|
+
const task = (await this.graphql(TASK_DETAIL_QUERY, { key: taskRef.key }, "Task")).data?.task;
|
|
1170
|
+
if (!task) throw new Error(`ONES: Task "${params.id}" not found`);
|
|
1171
|
+
const relatedActivities = shouldFetchRelatedActivities ? await this.fetchRelatedActivities(taskRef.key) : [];
|
|
856
1172
|
const wikiRefs = /* @__PURE__ */ new Map();
|
|
857
1173
|
for (const wiki of task.relatedWikiPages ?? []) if (!wiki.errorMessage) wikiRefs.set(wiki.uuid, {
|
|
858
1174
|
title: wiki.title,
|
|
@@ -893,6 +1209,18 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
893
1209
|
parts.push(`- #${related.number} ${related.name} [${related.issueType?.name}] (${related.status?.name}) — ${assignee}`);
|
|
894
1210
|
}
|
|
895
1211
|
}
|
|
1212
|
+
if (relatedActivities.length) {
|
|
1213
|
+
parts.push("");
|
|
1214
|
+
parts.push("## Related Work Items");
|
|
1215
|
+
for (const activity of relatedActivities) {
|
|
1216
|
+
const details = [
|
|
1217
|
+
`UUID: ${activity.uuid}`,
|
|
1218
|
+
activity.projectUUID ? `Project: ${activity.projectUUID}` : null,
|
|
1219
|
+
activity.relatedChild ? `Relation: ${activity.relatedChild}` : null
|
|
1220
|
+
].filter(Boolean);
|
|
1221
|
+
parts.push(`- ${activity.name} (${details.join(", ")})`);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
896
1224
|
if (task.parent?.uuid) {
|
|
897
1225
|
parts.push("");
|
|
898
1226
|
parts.push("## Parent Task");
|
|
@@ -923,7 +1251,12 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
923
1251
|
parts.push(detailText);
|
|
924
1252
|
}
|
|
925
1253
|
const wikiAttachments = wikiContents.flatMap((wiki) => wiki.attachments);
|
|
926
|
-
|
|
1254
|
+
const req = toRequirement(task, parts.join("\n"), wikiAttachments);
|
|
1255
|
+
req.raw = {
|
|
1256
|
+
...req.raw,
|
|
1257
|
+
relatedActivities
|
|
1258
|
+
};
|
|
1259
|
+
return req;
|
|
927
1260
|
}
|
|
928
1261
|
/**
|
|
929
1262
|
* Search tasks assigned to current user via GraphQL.
|
|
@@ -987,6 +1320,69 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
987
1320
|
pageSize
|
|
988
1321
|
};
|
|
989
1322
|
}
|
|
1323
|
+
async addManhour(params) {
|
|
1324
|
+
const description = params.description.trim();
|
|
1325
|
+
if (!description) throw new Error("ONES: description is required");
|
|
1326
|
+
const taskRef = await this.resolveTaskRef(params.taskId);
|
|
1327
|
+
const onesHours = toOnesHours(params.hours);
|
|
1328
|
+
const workDate = parseManhourDate(params.date);
|
|
1329
|
+
const key = (await this.graphql(ADD_MANHOUR_MUTATION, {
|
|
1330
|
+
mode: "simple",
|
|
1331
|
+
type: "recorded",
|
|
1332
|
+
customData: {},
|
|
1333
|
+
owner: (await this.login()).userUuid,
|
|
1334
|
+
task: taskRef.uuid,
|
|
1335
|
+
start_time: workDate.startTime,
|
|
1336
|
+
hours: onesHours,
|
|
1337
|
+
description
|
|
1338
|
+
}, "add-manhour")).data?.addManhour?.key;
|
|
1339
|
+
if (!key) throw new Error("ONES: Failed to add manhour");
|
|
1340
|
+
return {
|
|
1341
|
+
key,
|
|
1342
|
+
taskUuid: taskRef.uuid,
|
|
1343
|
+
hours: params.hours,
|
|
1344
|
+
description,
|
|
1345
|
+
date: workDate.date
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
async updateTaskPlanDates(params) {
|
|
1349
|
+
const planStartDate = params.planStartDate?.trim();
|
|
1350
|
+
const planEndDate = params.planEndDate?.trim();
|
|
1351
|
+
if (!planStartDate && !planEndDate) throw new Error("ONES: planStartDate or planEndDate is required");
|
|
1352
|
+
if (planStartDate && !isValidOnesDate(planStartDate)) throw new Error("ONES: planStartDate must be a valid YYYY-MM-DD date");
|
|
1353
|
+
if (planEndDate && !isValidOnesDate(planEndDate)) throw new Error("ONES: planEndDate must be a valid YYYY-MM-DD date");
|
|
1354
|
+
const taskRef = await this.resolveTaskRef(params.taskId);
|
|
1355
|
+
const session = await this.login();
|
|
1356
|
+
const fieldValues = [];
|
|
1357
|
+
if (planStartDate) fieldValues.push({
|
|
1358
|
+
field_uuid: "field027",
|
|
1359
|
+
value: planStartDate
|
|
1360
|
+
});
|
|
1361
|
+
if (planEndDate) fieldValues.push({
|
|
1362
|
+
field_uuid: "field028",
|
|
1363
|
+
value: planEndDate
|
|
1364
|
+
});
|
|
1365
|
+
const response = await fetch(`${this.config.apiBase}/project/api/project/team/${session.teamUuid}/tasks/update3`, {
|
|
1366
|
+
method: "POST",
|
|
1367
|
+
headers: {
|
|
1368
|
+
"Authorization": `Bearer ${session.accessToken}`,
|
|
1369
|
+
"Content-Type": "application/json"
|
|
1370
|
+
},
|
|
1371
|
+
body: JSON.stringify({ tasks: [{
|
|
1372
|
+
uuid: taskRef.uuid,
|
|
1373
|
+
field_values: fieldValues
|
|
1374
|
+
}] })
|
|
1375
|
+
});
|
|
1376
|
+
if (!response.ok) {
|
|
1377
|
+
const text = await response.text().catch(() => "");
|
|
1378
|
+
throw new Error(`ONES: Failed to update task plan dates: ${response.status} ${text}`);
|
|
1379
|
+
}
|
|
1380
|
+
return {
|
|
1381
|
+
taskUuid: taskRef.uuid,
|
|
1382
|
+
planStartDate: planStartDate ?? null,
|
|
1383
|
+
planEndDate: planEndDate ?? null
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
990
1386
|
async getRelatedIssues(params) {
|
|
991
1387
|
const session = await this.login();
|
|
992
1388
|
const taskKey = params.taskId.startsWith("task-") ? params.taskId : `task-${params.taskId}`;
|
|
@@ -1163,7 +1559,6 @@ var OnesAdapter = class extends BaseAdapter {
|
|
|
1163
1559
|
};
|
|
1164
1560
|
}
|
|
1165
1561
|
};
|
|
1166
|
-
|
|
1167
1562
|
//#endregion
|
|
1168
1563
|
//#region src/adapters/index.ts
|
|
1169
1564
|
const ADAPTER_MAP = { ones: OnesAdapter };
|
|
@@ -1175,7 +1570,6 @@ function createAdapter(sourceType, config, resolvedAuth) {
|
|
|
1175
1570
|
if (!AdapterClass) throw new Error(`Unsupported source type: "${sourceType}". Supported: ${Object.keys(ADAPTER_MAP).join(", ")}`);
|
|
1176
1571
|
return new AdapterClass(sourceType, config, resolvedAuth);
|
|
1177
1572
|
}
|
|
1178
|
-
|
|
1179
1573
|
//#endregion
|
|
1180
1574
|
//#region src/config/loader.ts
|
|
1181
1575
|
const AuthSchema = zod_v4.z.discriminatedUnion("type", [
|
|
@@ -1333,7 +1727,41 @@ function loadConfig(startDir) {
|
|
|
1333
1727
|
configPath
|
|
1334
1728
|
};
|
|
1335
1729
|
}
|
|
1336
|
-
|
|
1730
|
+
//#endregion
|
|
1731
|
+
//#region src/tools/add-manhour.ts
|
|
1732
|
+
const AddManhourSchema = zod_v4.z.object({
|
|
1733
|
+
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\")"),
|
|
1734
|
+
hours: zod_v4.z.number().positive().describe("Work hours to record. Natural hours are converted to ONES internal units."),
|
|
1735
|
+
description: zod_v4.z.string().min(1).describe("Work log description."),
|
|
1736
|
+
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."),
|
|
1737
|
+
source: zod_v4.z.string().optional().describe("Source to update. If omitted, uses the default source.")
|
|
1738
|
+
});
|
|
1739
|
+
async function handleAddManhour(input, adapters, defaultSource) {
|
|
1740
|
+
const sourceType = input.source ?? defaultSource;
|
|
1741
|
+
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
1742
|
+
const adapter = adapters.get(sourceType);
|
|
1743
|
+
if (!adapter) throw new Error(`Source "${sourceType}" is not configured. Available: ${[...adapters.keys()].join(", ")}`);
|
|
1744
|
+
return { content: [{
|
|
1745
|
+
type: "text",
|
|
1746
|
+
text: formatAddManhourResult(await adapter.addManhour({
|
|
1747
|
+
taskId: input.taskId,
|
|
1748
|
+
hours: input.hours,
|
|
1749
|
+
description: input.description,
|
|
1750
|
+
date: input.date
|
|
1751
|
+
}))
|
|
1752
|
+
}] };
|
|
1753
|
+
}
|
|
1754
|
+
function formatAddManhourResult(result) {
|
|
1755
|
+
return [
|
|
1756
|
+
"Added manhour.",
|
|
1757
|
+
"",
|
|
1758
|
+
`- **Key**: ${result.key}`,
|
|
1759
|
+
`- **Task UUID**: ${result.taskUuid}`,
|
|
1760
|
+
`- **Hours**: ${result.hours}`,
|
|
1761
|
+
`- **Date**: ${result.date ?? "today"}`,
|
|
1762
|
+
`- **Description**: ${result.description}`
|
|
1763
|
+
].join("\n");
|
|
1764
|
+
}
|
|
1337
1765
|
//#endregion
|
|
1338
1766
|
//#region src/tools/get-issue-detail.ts
|
|
1339
1767
|
const GetIssueDetailSchema = zod_v4.z.object({
|
|
@@ -1408,7 +1836,6 @@ function formatIssueDetail(detail) {
|
|
|
1408
1836
|
else lines.push("_No description_");
|
|
1409
1837
|
return lines.join("\n");
|
|
1410
1838
|
}
|
|
1411
|
-
|
|
1412
1839
|
//#endregion
|
|
1413
1840
|
//#region src/tools/get-related-issues.ts
|
|
1414
1841
|
const GetRelatedIssuesSchema = zod_v4.z.object({
|
|
@@ -1449,7 +1876,6 @@ function formatRelatedIssues(issues) {
|
|
|
1449
1876
|
}
|
|
1450
1877
|
return lines.join("\n");
|
|
1451
1878
|
}
|
|
1452
|
-
|
|
1453
1879
|
//#endregion
|
|
1454
1880
|
//#region src/tools/get-requirement.ts
|
|
1455
1881
|
const GetRequirementSchema = zod_v4.z.object({
|
|
@@ -1528,7 +1954,6 @@ function formatRequirement(req) {
|
|
|
1528
1954
|
}
|
|
1529
1955
|
return lines.join("\n");
|
|
1530
1956
|
}
|
|
1531
|
-
|
|
1532
1957
|
//#endregion
|
|
1533
1958
|
//#region src/tools/get-testcases.ts
|
|
1534
1959
|
const GetTestcasesSchema = zod_v4.z.object({
|
|
@@ -1580,7 +2005,6 @@ function formatTestcases(result) {
|
|
|
1580
2005
|
}
|
|
1581
2006
|
return lines.join("\n");
|
|
1582
2007
|
}
|
|
1583
|
-
|
|
1584
2008
|
//#endregion
|
|
1585
2009
|
//#region src/tools/list-sources.ts
|
|
1586
2010
|
async function handleListSources(adapters, config) {
|
|
@@ -1606,7 +2030,6 @@ async function handleListSources(adapters, config) {
|
|
|
1606
2030
|
text: lines.join("\n")
|
|
1607
2031
|
}] };
|
|
1608
2032
|
}
|
|
1609
|
-
|
|
1610
2033
|
//#endregion
|
|
1611
2034
|
//#region src/tools/search-requirements.ts
|
|
1612
2035
|
const SearchRequirementsSchema = zod_v4.z.object({
|
|
@@ -1647,7 +2070,39 @@ async function handleSearchRequirements(input, adapters, defaultSource) {
|
|
|
1647
2070
|
text: lines.join("\n")
|
|
1648
2071
|
}] };
|
|
1649
2072
|
}
|
|
1650
|
-
|
|
2073
|
+
//#endregion
|
|
2074
|
+
//#region src/tools/update-task-plan-dates.ts
|
|
2075
|
+
const DateSchema = zod_v4.z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Expected YYYY-MM-DD");
|
|
2076
|
+
const UpdateTaskPlanDatesSchema = zod_v4.z.object({
|
|
2077
|
+
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\")"),
|
|
2078
|
+
planStartDate: DateSchema.optional().describe("Plan start date in YYYY-MM-DD format."),
|
|
2079
|
+
planEndDate: DateSchema.optional().describe("Plan end date in YYYY-MM-DD format."),
|
|
2080
|
+
source: zod_v4.z.string().optional().describe("Source to update. If omitted, uses the default source.")
|
|
2081
|
+
});
|
|
2082
|
+
async function handleUpdateTaskPlanDates(input, adapters, defaultSource) {
|
|
2083
|
+
const sourceType = input.source ?? defaultSource;
|
|
2084
|
+
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
2085
|
+
const adapter = adapters.get(sourceType);
|
|
2086
|
+
if (!adapter) throw new Error(`Source "${sourceType}" is not configured. Available: ${[...adapters.keys()].join(", ")}`);
|
|
2087
|
+
return { content: [{
|
|
2088
|
+
type: "text",
|
|
2089
|
+
text: formatUpdateTaskPlanDatesResult(await adapter.updateTaskPlanDates({
|
|
2090
|
+
taskId: input.taskId,
|
|
2091
|
+
planStartDate: input.planStartDate,
|
|
2092
|
+
planEndDate: input.planEndDate
|
|
2093
|
+
}))
|
|
2094
|
+
}] };
|
|
2095
|
+
}
|
|
2096
|
+
function formatUpdateTaskPlanDatesResult(result) {
|
|
2097
|
+
const lines = [
|
|
2098
|
+
"Updated task plan dates.",
|
|
2099
|
+
"",
|
|
2100
|
+
`- **Task UUID**: ${result.taskUuid}`
|
|
2101
|
+
];
|
|
2102
|
+
if (result.planStartDate) lines.push(`- **Plan Start Date**: ${result.planStartDate}`);
|
|
2103
|
+
if (result.planEndDate) lines.push(`- **Plan End Date**: ${result.planEndDate}`);
|
|
2104
|
+
return lines.join("\n");
|
|
2105
|
+
}
|
|
1651
2106
|
//#endregion
|
|
1652
2107
|
//#region src/index.ts
|
|
1653
2108
|
/**
|
|
@@ -1695,7 +2150,10 @@ async function main() {
|
|
|
1695
2150
|
name: "ai-dev-requirements",
|
|
1696
2151
|
version: "0.1.0"
|
|
1697
2152
|
});
|
|
1698
|
-
server.
|
|
2153
|
+
server.registerTool("get_requirement", {
|
|
2154
|
+
description: "Fetch a single requirement/issue by its ID from a configured source (ONES)",
|
|
2155
|
+
inputSchema: GetRequirementSchema.shape
|
|
2156
|
+
}, async (params) => {
|
|
1699
2157
|
try {
|
|
1700
2158
|
return await handleGetRequirement(params, adapters, config.config.defaultSource);
|
|
1701
2159
|
} catch (err) {
|
|
@@ -1708,7 +2166,10 @@ async function main() {
|
|
|
1708
2166
|
};
|
|
1709
2167
|
}
|
|
1710
2168
|
});
|
|
1711
|
-
server.
|
|
2169
|
+
server.registerTool("search_requirements", {
|
|
2170
|
+
description: "Search for requirements/issues by keywords across a configured source",
|
|
2171
|
+
inputSchema: SearchRequirementsSchema.shape
|
|
2172
|
+
}, async (params) => {
|
|
1712
2173
|
try {
|
|
1713
2174
|
return await handleSearchRequirements(params, adapters, config.config.defaultSource);
|
|
1714
2175
|
} catch (err) {
|
|
@@ -1721,7 +2182,7 @@ async function main() {
|
|
|
1721
2182
|
};
|
|
1722
2183
|
}
|
|
1723
2184
|
});
|
|
1724
|
-
server.
|
|
2185
|
+
server.registerTool("list_sources", { description: "List all configured requirement sources and their status" }, async () => {
|
|
1725
2186
|
try {
|
|
1726
2187
|
return await handleListSources(adapters, config.config);
|
|
1727
2188
|
} catch (err) {
|
|
@@ -1734,7 +2195,10 @@ async function main() {
|
|
|
1734
2195
|
};
|
|
1735
2196
|
}
|
|
1736
2197
|
});
|
|
1737
|
-
server.
|
|
2198
|
+
server.registerTool("get_related_issues", {
|
|
2199
|
+
description: "Get pending defect issues (bugs) related to a requirement task. Returns all pending defects grouped by assignee (current user first).",
|
|
2200
|
+
inputSchema: GetRelatedIssuesSchema.shape
|
|
2201
|
+
}, async (params) => {
|
|
1738
2202
|
try {
|
|
1739
2203
|
return await handleGetRelatedIssues(params, adapters, config.config.defaultSource);
|
|
1740
2204
|
} catch (err) {
|
|
@@ -1747,7 +2211,10 @@ async function main() {
|
|
|
1747
2211
|
};
|
|
1748
2212
|
}
|
|
1749
2213
|
});
|
|
1750
|
-
server.
|
|
2214
|
+
server.registerTool("get_issue_detail", {
|
|
2215
|
+
description: "Get detailed information about a specific issue/defect including description, rich text, and images",
|
|
2216
|
+
inputSchema: GetIssueDetailSchema.shape
|
|
2217
|
+
}, async (params) => {
|
|
1751
2218
|
try {
|
|
1752
2219
|
return await handleGetIssueDetail(params, adapters, config.config.defaultSource);
|
|
1753
2220
|
} catch (err) {
|
|
@@ -1760,7 +2227,10 @@ async function main() {
|
|
|
1760
2227
|
};
|
|
1761
2228
|
}
|
|
1762
2229
|
});
|
|
1763
|
-
server.
|
|
2230
|
+
server.registerTool("get_testcases", {
|
|
2231
|
+
description: "Get all test cases for a task by its number (e.g. 302). Searches the testcase library for a matching module and returns all cases with steps.",
|
|
2232
|
+
inputSchema: GetTestcasesSchema.shape
|
|
2233
|
+
}, async (params) => {
|
|
1764
2234
|
try {
|
|
1765
2235
|
return await handleGetTestcases(params, adapters, config.config.defaultSource);
|
|
1766
2236
|
} catch (err) {
|
|
@@ -1773,6 +2243,38 @@ async function main() {
|
|
|
1773
2243
|
};
|
|
1774
2244
|
}
|
|
1775
2245
|
});
|
|
2246
|
+
server.registerTool("add_manhour", {
|
|
2247
|
+
description: "Add a work-hour record to a ONES task, bug, or requirement. Supports task key, uuid, number, or displayId.",
|
|
2248
|
+
inputSchema: AddManhourSchema.shape
|
|
2249
|
+
}, async (params) => {
|
|
2250
|
+
try {
|
|
2251
|
+
return await handleAddManhour(params, adapters, config.config.defaultSource);
|
|
2252
|
+
} catch (err) {
|
|
2253
|
+
return {
|
|
2254
|
+
content: [{
|
|
2255
|
+
type: "text",
|
|
2256
|
+
text: `Error: ${err.message}`
|
|
2257
|
+
}],
|
|
2258
|
+
isError: true
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
});
|
|
2262
|
+
server.registerTool("update_task_plan_dates", {
|
|
2263
|
+
description: "Update plan start and/or plan end dates for a ONES task, bug, or requirement. Supports task key, uuid, number, or displayId.",
|
|
2264
|
+
inputSchema: UpdateTaskPlanDatesSchema.shape
|
|
2265
|
+
}, async (params) => {
|
|
2266
|
+
try {
|
|
2267
|
+
return await handleUpdateTaskPlanDates(params, adapters, config.config.defaultSource);
|
|
2268
|
+
} catch (err) {
|
|
2269
|
+
return {
|
|
2270
|
+
content: [{
|
|
2271
|
+
type: "text",
|
|
2272
|
+
text: `Error: ${err.message}`
|
|
2273
|
+
}],
|
|
2274
|
+
isError: true
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
1776
2278
|
const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
|
|
1777
2279
|
await server.connect(transport);
|
|
1778
2280
|
}
|
|
@@ -1780,5 +2282,4 @@ main().catch((err) => {
|
|
|
1780
2282
|
console.error("[requirements-mcp] Fatal error:", err);
|
|
1781
2283
|
process.exit(1);
|
|
1782
2284
|
});
|
|
1783
|
-
|
|
1784
|
-
//#endregion
|
|
2285
|
+
//#endregion
|