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 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
- for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
- key = keys[i];
13
- if (!__hasOwnProp.call(to, key) && key !== except) {
14
- __defProp(to, key, {
15
- get: ((k) => from[k]).bind(null, key),
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.includes("/wiki/")) return null;
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 input.includes("/wiki/");
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(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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 cols = typeof block.cols === "number" && block.cols > 0 ? Math.trunc(block.cols) : 0;
414
- const children = Array.isArray(block.children) ? block.children.filter((child) => typeof child === "string") : [];
415
- if (!cols || !children.length) return "";
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 (let index = 0; index < children.length; index += cols) {
418
- const cells = children.slice(index, index + cols).map((childId) => escapeWikiTableCell(renderWikiCell(document[childId], document, context)));
419
- while (cells.length < cols) cells.push("");
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: cols }, () => "---").join(" | ")} |`);
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
- const authRequestId = new URL(authorizeLocation).searchParams.get("id");
548
- if (!authRequestId) throw new Error("ONES: Cannot parse auth_request_id from authorize redirect");
549
- const finalizeRes = await fetch(`${baseUrl}/identity/api/auth_request/finalize`, {
550
- method: "POST",
551
- headers: {
552
- "Content-Type": "application/json;charset=UTF-8",
553
- "Cookie": cookies
554
- },
555
- body: JSON.stringify({
556
- auth_request_id: authRequestId,
557
- region_uuid: orgUser.region_uuid,
558
- org_uuid: orgUser.org_uuid,
559
- org_user_uuid: orgUser.org_user.org_user_uuid
560
- })
561
- });
562
- if (!finalizeRes.ok) {
563
- const text = await finalizeRes.text().catch(() => "");
564
- throw new Error(`ONES: Finalize failed: ${finalizeRes.status} ${text}`);
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
- this.issueTypesCache = (await this.graphql(ISSUE_TYPES_QUERY, { orderBy: { namePinyin: "ASC" } }, "issueTypes")).data?.issueTypes ?? [];
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
- let taskUuid = params.id;
836
- const numMatch = taskUuid.match(/^#?(\d+)$/);
837
- if (numMatch) {
838
- const taskNumber = Number.parseInt(numMatch[1], 10);
839
- const found = ((await this.graphql(TASK_BY_NUMBER_QUERY, {
840
- groupBy: { tasks: {} },
841
- groupOrderBy: null,
842
- orderBy: { createTime: "DESC" },
843
- filterGroup: [{ number_in: [taskNumber] }],
844
- search: null,
845
- pagination: {
846
- limit: 10,
847
- preciseCount: false
848
- },
849
- limit: 10
850
- }, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? []).find((t) => t.number === taskNumber);
851
- if (!found) throw new Error(`ONES: Task #${taskNumber} not found in current team`);
852
- taskUuid = found.uuid;
853
- }
854
- const task = (await this.graphql(TASK_DETAIL_QUERY, { key: `task-${taskUuid}` }, "Task")).data?.task;
855
- if (!task) throw new Error(`ONES: Task "${taskUuid}" not found`);
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
- return toRequirement(task, parts.join("\n"), wikiAttachments);
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.tool("get_requirement", "Fetch a single requirement/issue by its ID from a configured source (ONES)", GetRequirementSchema.shape, async (params) => {
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.tool("search_requirements", "Search for requirements/issues by keywords across a configured source", SearchRequirementsSchema.shape, async (params) => {
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.tool("list_sources", "List all configured requirement sources and their status", {}, async () => {
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.tool("get_related_issues", "Get pending defect issues (bugs) related to a requirement task. Returns all pending defects grouped by assignee (current user first).", GetRelatedIssuesSchema.shape, async (params) => {
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.tool("get_issue_detail", "Get detailed information about a specific issue/defect including description, rich text, and images", GetIssueDetailSchema.shape, async (params) => {
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.tool("get_testcases", "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.", GetTestcasesSchema.shape, async (params) => {
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