ai-dev-requirements 0.2.0 → 0.2.1

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
@@ -188,7 +188,7 @@ function loadConfig(startDir) {
188
188
  }
189
189
  //#endregion
190
190
  //#region package.json
191
- var version = "0.2.0";
191
+ var version = "0.2.1";
192
192
  //#endregion
193
193
  //#region ../../src/utils/map-status.ts
194
194
  const ONES_STATUS_MAP = {
@@ -693,6 +693,21 @@ function htmlToPlainText(html) {
693
693
  function getTaskDetailText(task) {
694
694
  return task.descriptionText?.trim() || htmlToPlainText(task.desc_rich ?? task.description ?? "");
695
695
  }
696
+ function extractHtmlImageReferences(html) {
697
+ return Array.from(html.matchAll(/<img\b[^>]*>/gi), (match) => {
698
+ const tag = match[0];
699
+ const srcMatch = tag.match(/\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)')/i);
700
+ const resourceMatch = tag.match(/\bdata-uuid\s*=\s*(?:"([^"]*)"|'([^']*)')/i);
701
+ return {
702
+ tag,
703
+ src: (srcMatch?.[1] ?? srcMatch?.[2] ?? "").replace(/&amp;/gi, "&").trim(),
704
+ resourceUuid: (resourceMatch?.[1] ?? resourceMatch?.[2] ?? "").trim()
705
+ };
706
+ });
707
+ }
708
+ function containsInlineTaskImages(task) {
709
+ return [task.description, task.desc_rich].some((value) => typeof value === "string" && /<img\b/i.test(value)) || /\[(?:image|图片)\]/i.test(task.descriptionText ?? "");
710
+ }
696
711
  function isRecord(value) {
697
712
  return value !== null && typeof value === "object" && !Array.isArray(value);
698
713
  }
@@ -1131,7 +1146,11 @@ var OnesAdapter = class extends BaseAdapter {
1131
1146
  return response.json();
1132
1147
  }
1133
1148
  async fetchRelatedActivities(taskKey) {
1134
- return (await this.onesql(RELATED_ACTIVITIES_QUERY, { key: taskKey }, "Task")).data?.task?.relatedActivities ?? [];
1149
+ try {
1150
+ return (await this.onesql(RELATED_ACTIVITIES_QUERY, { key: taskKey }, "Task")).data?.task?.relatedActivities ?? [];
1151
+ } catch {
1152
+ return [];
1153
+ }
1135
1154
  }
1136
1155
  async searchTaskByNumber(taskNumber) {
1137
1156
  const session = await this.login();
@@ -1316,30 +1335,92 @@ var OnesAdapter = class extends BaseAdapter {
1316
1335
  return null;
1317
1336
  }
1318
1337
  }
1338
+ getAttachmentResourceUuid(image) {
1339
+ if (image.src) try {
1340
+ const source = new URL(image.src, this.config.apiBase);
1341
+ if (source.origin === new URL(this.config.apiBase).origin) {
1342
+ const match = source.pathname.match(/\/res\/attachment\/([^/]+)$/);
1343
+ const resourceUuid = match?.[1] ? decodeOnesPathIdentifier(match[1]) : null;
1344
+ if (resourceUuid) return resourceUuid;
1345
+ }
1346
+ } catch {}
1347
+ return image.resourceUuid;
1348
+ }
1319
1349
  /**
1320
1350
  * Replace stale image URLs in HTML with fresh signed URLs from the attachment API.
1321
- * Extracts data-uuid from <img> tags and resolves fresh URLs in parallel.
1351
+ * Prefer the resource identifier from the attachment URL because ONES data-uuid
1352
+ * can identify the editor node instead of the underlying attachment.
1322
1353
  */
1323
- async refreshImageUrls(html) {
1354
+ async refreshImageUrls(html, freshUrlCache = /* @__PURE__ */ new Map()) {
1324
1355
  if (!html) return html;
1325
- const matches = Array.from(html.matchAll(/<img\s[^>]*data-uuid="([^"]+)"[^>]*>/g));
1326
- if (matches.length === 0) return html;
1327
- const replacements = await Promise.all(matches.map(async (match) => {
1328
- const dataUuid = match[1];
1329
- const freshUrl = await this.getAttachmentUrl(dataUuid);
1356
+ const images = extractHtmlImageReferences(html).flatMap((image) => {
1357
+ const resourceUuid = this.getAttachmentResourceUuid(image);
1358
+ return resourceUuid ? [{
1359
+ image,
1360
+ resourceUuid
1361
+ }] : [];
1362
+ });
1363
+ if (images.length === 0) return html;
1364
+ const replacements = await Promise.all(images.map(async ({ image, resourceUuid }) => {
1365
+ let freshUrl = freshUrlCache.get(resourceUuid);
1366
+ if (!freshUrl) {
1367
+ freshUrl = this.getAttachmentUrl(resourceUuid);
1368
+ freshUrlCache.set(resourceUuid, freshUrl);
1369
+ }
1330
1370
  return {
1331
- fullMatch: match[0],
1332
- dataUuid,
1333
- freshUrl
1371
+ fullMatch: image.tag,
1372
+ freshUrl: await freshUrl
1334
1373
  };
1335
1374
  }));
1336
1375
  let result = html;
1337
- for (const { fullMatch, freshUrl } of replacements) if (freshUrl) {
1338
- const updatedImg = fullMatch.replace(/src="[^"]*"/, `src="${freshUrl}"`);
1376
+ for (const { fullMatch, freshUrl } of replacements) {
1377
+ if (!freshUrl) continue;
1378
+ const updatedImg = /\bsrc\s*=/i.test(fullMatch) ? fullMatch.replace(/\bsrc\s*=\s*(?:"[^"]*"|'[^']*')/i, `src="${freshUrl}"`) : fullMatch.replace(/<img\b/i, `<img src="${freshUrl}"`);
1339
1379
  result = result.replace(fullMatch, updatedImg);
1340
1380
  }
1341
1381
  return result;
1342
1382
  }
1383
+ async getFreshTaskDescriptions(task) {
1384
+ const taskInfo = await this.fetchTaskInfo(task.uuid);
1385
+ const rawDescription = typeof taskInfo.desc === "string" ? taskInfo.desc : task.description ?? "";
1386
+ const rawDescriptionRich = typeof taskInfo.desc_rich === "string" ? taskInfo.desc_rich : task.desc_rich ?? task.description ?? "";
1387
+ const freshUrlCache = /* @__PURE__ */ new Map();
1388
+ const [description, descriptionRich] = await Promise.all([this.refreshImageUrls(rawDescription, freshUrlCache), this.refreshImageUrls(rawDescriptionRich, freshUrlCache)]);
1389
+ return {
1390
+ description,
1391
+ descriptionRich
1392
+ };
1393
+ }
1394
+ async getTaskImageAttachments(task) {
1395
+ const { description, descriptionRich } = await this.getFreshTaskDescriptions(task);
1396
+ const images = [...extractHtmlImageReferences(descriptionRich), ...extractHtmlImageReferences(description)];
1397
+ const seen = /* @__PURE__ */ new Set();
1398
+ const attachments = [];
1399
+ for (const image of images) {
1400
+ if (!image.src) continue;
1401
+ let url;
1402
+ try {
1403
+ url = new URL(image.src, this.config.apiBase).toString();
1404
+ } catch {
1405
+ continue;
1406
+ }
1407
+ if (this.classifyRemoteImageUrl(url) === "untrusted") continue;
1408
+ const identity = image.resourceUuid || url;
1409
+ if (seen.has(identity)) continue;
1410
+ seen.add(identity);
1411
+ const pathname = new URL(url).pathname;
1412
+ const pathName = attachmentNameFromPath(pathname);
1413
+ const name = pathName && pathName !== "/" ? pathName : `image-${attachments.length + 1}.png`;
1414
+ attachments.push({
1415
+ id: image.resourceUuid || `${task.uuid}-image-${attachments.length + 1}`,
1416
+ name,
1417
+ url,
1418
+ mimeType: mimeTypeFromFileName(pathname),
1419
+ size: 0
1420
+ });
1421
+ }
1422
+ return attachments;
1423
+ }
1343
1424
  /**
1344
1425
  * Fetch wiki page content via REST API.
1345
1426
  * Endpoint: /wiki/api/wiki/team/{teamUuid}/online_page/{wikiUuid}/content
@@ -1459,7 +1540,7 @@ var OnesAdapter = class extends BaseAdapter {
1459
1540
  title: `Wiki ${wikiUuid}`,
1460
1541
  uuid: wikiUuid
1461
1542
  });
1462
- const wikiContents = await Promise.all([...wikiRefs.values()].map(async (wiki) => {
1543
+ const [wikiContents, taskImageAttachments] = await Promise.all([Promise.all([...wikiRefs.values()].map(async (wiki) => {
1463
1544
  const rendered = await this.fetchWikiContent(wiki.uuid);
1464
1545
  return {
1465
1546
  title: wiki.title,
@@ -1467,7 +1548,7 @@ var OnesAdapter = class extends BaseAdapter {
1467
1548
  content: rendered.content,
1468
1549
  attachments: rendered.attachments
1469
1550
  };
1470
- }));
1551
+ })), containsInlineTaskImages(task) ? this.getTaskImageAttachments(task) : Promise.resolve([])]);
1471
1552
  const parts = [];
1472
1553
  parts.push(`# #${task.number} ${task.name}`);
1473
1554
  parts.push("");
@@ -1527,7 +1608,7 @@ var OnesAdapter = class extends BaseAdapter {
1527
1608
  parts.push(detailText);
1528
1609
  }
1529
1610
  const wikiAttachments = wikiContents.flatMap((wiki) => wiki.attachments);
1530
- const req = toRequirement(task, parts.join("\n"), wikiAttachments);
1611
+ const req = toRequirement(task, parts.join("\n"), [...wikiAttachments, ...taskImageAttachments]);
1531
1612
  req.raw = {
1532
1613
  ...req.raw,
1533
1614
  relatedActivities,
@@ -1735,35 +1816,13 @@ var OnesAdapter = class extends BaseAdapter {
1735
1816
  }));
1736
1817
  }
1737
1818
  async getIssueDetail(params) {
1738
- let issueKey;
1739
- const numMatch = params.issueId.match(/^#?(\d+)$/);
1740
- if (numMatch) {
1741
- const taskNumber = Number.parseInt(numMatch[1], 10);
1742
- const found = ((await this.graphql(SEARCH_TASKS_QUERY, {
1743
- groupBy: { tasks: {} },
1744
- groupOrderBy: null,
1745
- orderBy: { createTime: "DESC" },
1746
- filterGroup: [{ number_in: [taskNumber] }],
1747
- search: null,
1748
- pagination: {
1749
- limit: 10,
1750
- preciseCount: false
1751
- },
1752
- limit: 10
1753
- }, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? []).find((t) => t.number === taskNumber);
1754
- if (!found) throw new Error(`ONES: Issue #${taskNumber} not found in current team`);
1755
- issueKey = `task-${found.uuid}`;
1756
- } else issueKey = params.issueId.startsWith("task-") ? params.issueId : `task-${params.issueId}`;
1819
+ const { key: issueKey } = await this.resolveTaskRef(params.issueId);
1757
1820
  const task = (await this.graphql(ISSUE_DETAIL_QUERY, { key: issueKey }, "Task")).data?.task;
1758
1821
  if (!task) throw new Error(`ONES: Issue "${issueKey}" not found`);
1759
1822
  const kind = classifyOnesWorkItem(task.issueType, task.subIssueType);
1760
1823
  if (kind === "unknown") throw new Error(`ONES: Unable to classify "${params.issueId}" before get_issue_detail`);
1761
1824
  if (kind === "requirement" || kind === "task") throw unsupportedWorkItemToolError(params.issueId, kind, "get_issue_detail", "get_work_item");
1762
- const taskInfo = await this.fetchTaskInfo(task.uuid);
1763
- const rawDescription = taskInfo.desc ?? task.description ?? "";
1764
- const rawDescRich = taskInfo.desc_rich ?? task.desc_rich ?? "";
1765
- const freshDescription = await this.refreshImageUrls(rawDescription);
1766
- const freshDescRich = await this.refreshImageUrls(rawDescRich);
1825
+ const { description: freshDescription, descriptionRich: freshDescRich } = await this.getFreshTaskDescriptions(task);
1767
1826
  return {
1768
1827
  key: task.key,
1769
1828
  uuid: task.uuid,
@@ -2356,7 +2415,7 @@ async function downloadTrustedImages(urls, options) {
2356
2415
  //#endregion
2357
2416
  //#region ../../src/tools/get-issue-detail.ts
2358
2417
  const GetIssueDetailSchema = zod_v4.z.object({
2359
- issueId: zod_v4.z.string().describe("The issue task ID or key (e.g. \"mock-issue-uuid\" or \"task-mock-issue-uuid\")"),
2418
+ issueId: zod_v4.z.string().describe("ONES defect UUID, task key, number, or display ID (for example \"DEMO-2001\")"),
2360
2419
  source: zod_v4.z.string().optional().describe("Source to fetch from. If omitted, uses the default source.")
2361
2420
  });
2362
2421
  /**
package/dist/index.mjs CHANGED
@@ -165,7 +165,7 @@ function loadConfig(startDir) {
165
165
  }
166
166
  //#endregion
167
167
  //#region package.json
168
- var version = "0.2.0";
168
+ var version = "0.2.1";
169
169
  //#endregion
170
170
  //#region ../../src/utils/map-status.ts
171
171
  const ONES_STATUS_MAP = {
@@ -670,6 +670,21 @@ function htmlToPlainText(html) {
670
670
  function getTaskDetailText(task) {
671
671
  return task.descriptionText?.trim() || htmlToPlainText(task.desc_rich ?? task.description ?? "");
672
672
  }
673
+ function extractHtmlImageReferences(html) {
674
+ return Array.from(html.matchAll(/<img\b[^>]*>/gi), (match) => {
675
+ const tag = match[0];
676
+ const srcMatch = tag.match(/\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)')/i);
677
+ const resourceMatch = tag.match(/\bdata-uuid\s*=\s*(?:"([^"]*)"|'([^']*)')/i);
678
+ return {
679
+ tag,
680
+ src: (srcMatch?.[1] ?? srcMatch?.[2] ?? "").replace(/&amp;/gi, "&").trim(),
681
+ resourceUuid: (resourceMatch?.[1] ?? resourceMatch?.[2] ?? "").trim()
682
+ };
683
+ });
684
+ }
685
+ function containsInlineTaskImages(task) {
686
+ return [task.description, task.desc_rich].some((value) => typeof value === "string" && /<img\b/i.test(value)) || /\[(?:image|图片)\]/i.test(task.descriptionText ?? "");
687
+ }
673
688
  function isRecord(value) {
674
689
  return value !== null && typeof value === "object" && !Array.isArray(value);
675
690
  }
@@ -1108,7 +1123,11 @@ var OnesAdapter = class extends BaseAdapter {
1108
1123
  return response.json();
1109
1124
  }
1110
1125
  async fetchRelatedActivities(taskKey) {
1111
- return (await this.onesql(RELATED_ACTIVITIES_QUERY, { key: taskKey }, "Task")).data?.task?.relatedActivities ?? [];
1126
+ try {
1127
+ return (await this.onesql(RELATED_ACTIVITIES_QUERY, { key: taskKey }, "Task")).data?.task?.relatedActivities ?? [];
1128
+ } catch {
1129
+ return [];
1130
+ }
1112
1131
  }
1113
1132
  async searchTaskByNumber(taskNumber) {
1114
1133
  const session = await this.login();
@@ -1293,30 +1312,92 @@ var OnesAdapter = class extends BaseAdapter {
1293
1312
  return null;
1294
1313
  }
1295
1314
  }
1315
+ getAttachmentResourceUuid(image) {
1316
+ if (image.src) try {
1317
+ const source = new URL(image.src, this.config.apiBase);
1318
+ if (source.origin === new URL(this.config.apiBase).origin) {
1319
+ const match = source.pathname.match(/\/res\/attachment\/([^/]+)$/);
1320
+ const resourceUuid = match?.[1] ? decodeOnesPathIdentifier(match[1]) : null;
1321
+ if (resourceUuid) return resourceUuid;
1322
+ }
1323
+ } catch {}
1324
+ return image.resourceUuid;
1325
+ }
1296
1326
  /**
1297
1327
  * Replace stale image URLs in HTML with fresh signed URLs from the attachment API.
1298
- * Extracts data-uuid from <img> tags and resolves fresh URLs in parallel.
1328
+ * Prefer the resource identifier from the attachment URL because ONES data-uuid
1329
+ * can identify the editor node instead of the underlying attachment.
1299
1330
  */
1300
- async refreshImageUrls(html) {
1331
+ async refreshImageUrls(html, freshUrlCache = /* @__PURE__ */ new Map()) {
1301
1332
  if (!html) return html;
1302
- const matches = Array.from(html.matchAll(/<img\s[^>]*data-uuid="([^"]+)"[^>]*>/g));
1303
- if (matches.length === 0) return html;
1304
- const replacements = await Promise.all(matches.map(async (match) => {
1305
- const dataUuid = match[1];
1306
- const freshUrl = await this.getAttachmentUrl(dataUuid);
1333
+ const images = extractHtmlImageReferences(html).flatMap((image) => {
1334
+ const resourceUuid = this.getAttachmentResourceUuid(image);
1335
+ return resourceUuid ? [{
1336
+ image,
1337
+ resourceUuid
1338
+ }] : [];
1339
+ });
1340
+ if (images.length === 0) return html;
1341
+ const replacements = await Promise.all(images.map(async ({ image, resourceUuid }) => {
1342
+ let freshUrl = freshUrlCache.get(resourceUuid);
1343
+ if (!freshUrl) {
1344
+ freshUrl = this.getAttachmentUrl(resourceUuid);
1345
+ freshUrlCache.set(resourceUuid, freshUrl);
1346
+ }
1307
1347
  return {
1308
- fullMatch: match[0],
1309
- dataUuid,
1310
- freshUrl
1348
+ fullMatch: image.tag,
1349
+ freshUrl: await freshUrl
1311
1350
  };
1312
1351
  }));
1313
1352
  let result = html;
1314
- for (const { fullMatch, freshUrl } of replacements) if (freshUrl) {
1315
- const updatedImg = fullMatch.replace(/src="[^"]*"/, `src="${freshUrl}"`);
1353
+ for (const { fullMatch, freshUrl } of replacements) {
1354
+ if (!freshUrl) continue;
1355
+ const updatedImg = /\bsrc\s*=/i.test(fullMatch) ? fullMatch.replace(/\bsrc\s*=\s*(?:"[^"]*"|'[^']*')/i, `src="${freshUrl}"`) : fullMatch.replace(/<img\b/i, `<img src="${freshUrl}"`);
1316
1356
  result = result.replace(fullMatch, updatedImg);
1317
1357
  }
1318
1358
  return result;
1319
1359
  }
1360
+ async getFreshTaskDescriptions(task) {
1361
+ const taskInfo = await this.fetchTaskInfo(task.uuid);
1362
+ const rawDescription = typeof taskInfo.desc === "string" ? taskInfo.desc : task.description ?? "";
1363
+ const rawDescriptionRich = typeof taskInfo.desc_rich === "string" ? taskInfo.desc_rich : task.desc_rich ?? task.description ?? "";
1364
+ const freshUrlCache = /* @__PURE__ */ new Map();
1365
+ const [description, descriptionRich] = await Promise.all([this.refreshImageUrls(rawDescription, freshUrlCache), this.refreshImageUrls(rawDescriptionRich, freshUrlCache)]);
1366
+ return {
1367
+ description,
1368
+ descriptionRich
1369
+ };
1370
+ }
1371
+ async getTaskImageAttachments(task) {
1372
+ const { description, descriptionRich } = await this.getFreshTaskDescriptions(task);
1373
+ const images = [...extractHtmlImageReferences(descriptionRich), ...extractHtmlImageReferences(description)];
1374
+ const seen = /* @__PURE__ */ new Set();
1375
+ const attachments = [];
1376
+ for (const image of images) {
1377
+ if (!image.src) continue;
1378
+ let url;
1379
+ try {
1380
+ url = new URL(image.src, this.config.apiBase).toString();
1381
+ } catch {
1382
+ continue;
1383
+ }
1384
+ if (this.classifyRemoteImageUrl(url) === "untrusted") continue;
1385
+ const identity = image.resourceUuid || url;
1386
+ if (seen.has(identity)) continue;
1387
+ seen.add(identity);
1388
+ const pathname = new URL(url).pathname;
1389
+ const pathName = attachmentNameFromPath(pathname);
1390
+ const name = pathName && pathName !== "/" ? pathName : `image-${attachments.length + 1}.png`;
1391
+ attachments.push({
1392
+ id: image.resourceUuid || `${task.uuid}-image-${attachments.length + 1}`,
1393
+ name,
1394
+ url,
1395
+ mimeType: mimeTypeFromFileName(pathname),
1396
+ size: 0
1397
+ });
1398
+ }
1399
+ return attachments;
1400
+ }
1320
1401
  /**
1321
1402
  * Fetch wiki page content via REST API.
1322
1403
  * Endpoint: /wiki/api/wiki/team/{teamUuid}/online_page/{wikiUuid}/content
@@ -1436,7 +1517,7 @@ var OnesAdapter = class extends BaseAdapter {
1436
1517
  title: `Wiki ${wikiUuid}`,
1437
1518
  uuid: wikiUuid
1438
1519
  });
1439
- const wikiContents = await Promise.all([...wikiRefs.values()].map(async (wiki) => {
1520
+ const [wikiContents, taskImageAttachments] = await Promise.all([Promise.all([...wikiRefs.values()].map(async (wiki) => {
1440
1521
  const rendered = await this.fetchWikiContent(wiki.uuid);
1441
1522
  return {
1442
1523
  title: wiki.title,
@@ -1444,7 +1525,7 @@ var OnesAdapter = class extends BaseAdapter {
1444
1525
  content: rendered.content,
1445
1526
  attachments: rendered.attachments
1446
1527
  };
1447
- }));
1528
+ })), containsInlineTaskImages(task) ? this.getTaskImageAttachments(task) : Promise.resolve([])]);
1448
1529
  const parts = [];
1449
1530
  parts.push(`# #${task.number} ${task.name}`);
1450
1531
  parts.push("");
@@ -1504,7 +1585,7 @@ var OnesAdapter = class extends BaseAdapter {
1504
1585
  parts.push(detailText);
1505
1586
  }
1506
1587
  const wikiAttachments = wikiContents.flatMap((wiki) => wiki.attachments);
1507
- const req = toRequirement(task, parts.join("\n"), wikiAttachments);
1588
+ const req = toRequirement(task, parts.join("\n"), [...wikiAttachments, ...taskImageAttachments]);
1508
1589
  req.raw = {
1509
1590
  ...req.raw,
1510
1591
  relatedActivities,
@@ -1712,35 +1793,13 @@ var OnesAdapter = class extends BaseAdapter {
1712
1793
  }));
1713
1794
  }
1714
1795
  async getIssueDetail(params) {
1715
- let issueKey;
1716
- const numMatch = params.issueId.match(/^#?(\d+)$/);
1717
- if (numMatch) {
1718
- const taskNumber = Number.parseInt(numMatch[1], 10);
1719
- const found = ((await this.graphql(SEARCH_TASKS_QUERY, {
1720
- groupBy: { tasks: {} },
1721
- groupOrderBy: null,
1722
- orderBy: { createTime: "DESC" },
1723
- filterGroup: [{ number_in: [taskNumber] }],
1724
- search: null,
1725
- pagination: {
1726
- limit: 10,
1727
- preciseCount: false
1728
- },
1729
- limit: 10
1730
- }, "group-task-data")).data?.buckets?.flatMap((b) => b.tasks ?? []) ?? []).find((t) => t.number === taskNumber);
1731
- if (!found) throw new Error(`ONES: Issue #${taskNumber} not found in current team`);
1732
- issueKey = `task-${found.uuid}`;
1733
- } else issueKey = params.issueId.startsWith("task-") ? params.issueId : `task-${params.issueId}`;
1796
+ const { key: issueKey } = await this.resolveTaskRef(params.issueId);
1734
1797
  const task = (await this.graphql(ISSUE_DETAIL_QUERY, { key: issueKey }, "Task")).data?.task;
1735
1798
  if (!task) throw new Error(`ONES: Issue "${issueKey}" not found`);
1736
1799
  const kind = classifyOnesWorkItem(task.issueType, task.subIssueType);
1737
1800
  if (kind === "unknown") throw new Error(`ONES: Unable to classify "${params.issueId}" before get_issue_detail`);
1738
1801
  if (kind === "requirement" || kind === "task") throw unsupportedWorkItemToolError(params.issueId, kind, "get_issue_detail", "get_work_item");
1739
- const taskInfo = await this.fetchTaskInfo(task.uuid);
1740
- const rawDescription = taskInfo.desc ?? task.description ?? "";
1741
- const rawDescRich = taskInfo.desc_rich ?? task.desc_rich ?? "";
1742
- const freshDescription = await this.refreshImageUrls(rawDescription);
1743
- const freshDescRich = await this.refreshImageUrls(rawDescRich);
1802
+ const { description: freshDescription, descriptionRich: freshDescRich } = await this.getFreshTaskDescriptions(task);
1744
1803
  return {
1745
1804
  key: task.key,
1746
1805
  uuid: task.uuid,
@@ -2333,7 +2392,7 @@ async function downloadTrustedImages(urls, options) {
2333
2392
  //#endregion
2334
2393
  //#region ../../src/tools/get-issue-detail.ts
2335
2394
  const GetIssueDetailSchema = z.object({
2336
- issueId: z.string().describe("The issue task ID or key (e.g. \"mock-issue-uuid\" or \"task-mock-issue-uuid\")"),
2395
+ issueId: z.string().describe("ONES defect UUID, task key, number, or display ID (for example \"DEMO-2001\")"),
2337
2396
  source: z.string().optional().describe("Source to fetch from. If omitted, uses the default source.")
2338
2397
  });
2339
2398
  /**