@neobiotechlabs/neobiotech-dev-agent 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@
10
10
  "source": "npm",
11
11
  "package": "@neobiotechlabs/neobiotech-dev-agent",
12
12
  "registry": "https://registry.npmjs.org",
13
- "version": "0.1.10"
13
+ "version": "0.1.12"
14
14
  },
15
15
  "description": "의료기기 SW 규제 개발 자동화 (Jira·Confluence·Risk·CVSS·Xray·IEC 62304/62366·MDR·문서 렌더)",
16
16
  "category": "medical-device",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neobiotech-dev-agent",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "displayName": "NeoBiotech Dev Agent (MedDev)",
5
5
  "description": "의료기기 SW 규제 개발 자동화 — Jira·Confluence·Risk·CVSS·Xray·IEC 62304/62366·MDR·문서 렌더",
6
6
  "author": {
package/dist/index.js CHANGED
@@ -14666,6 +14666,9 @@ __export(markdown_exports, {
14666
14666
  function stripFrontmatter(md) {
14667
14667
  return md.replace(FRONTMATTER_RE, "");
14668
14668
  }
14669
+ function stripLeftoverStars(s2) {
14670
+ return s2.replace(/\*\*/g, "");
14671
+ }
14669
14672
  function inlineTokensToAdf(tokens, parentMarks) {
14670
14673
  if (!Array.isArray(tokens)) return [];
14671
14674
  const out = [];
@@ -14687,22 +14690,22 @@ function inlineTokensToAdf(tokens, parentMarks) {
14687
14690
  break;
14688
14691
  case "text":
14689
14692
  case "escape":
14690
- out.push({ type: "text", text: String(tok.text ?? ""), marks: parentMarks.length ? parentMarks : void 0 });
14693
+ out.push({ type: "text", text: stripLeftoverStars(String(tok.text ?? "")), marks: parentMarks.length ? parentMarks : void 0 });
14691
14694
  break;
14692
14695
  case "link":
14693
14696
  out.push(...inlineTokensToAdf(tok.tokens, parentMarks));
14694
14697
  break;
14695
14698
  case "image":
14696
- if (tok.text) out.push({ type: "text", text: String(tok.text), marks: parentMarks.length ? parentMarks : void 0 });
14699
+ if (tok.text) out.push({ type: "text", text: stripLeftoverStars(String(tok.text)), marks: parentMarks.length ? parentMarks : void 0 });
14697
14700
  break;
14698
14701
  case "br":
14699
14702
  out.push({ type: "text", text: "\n", marks: parentMarks.length ? parentMarks : void 0 });
14700
14703
  break;
14701
14704
  case "html":
14702
- out.push({ type: "text", text: String(tok.text ?? "").replace(/<[^>]+>/g, ""), marks: parentMarks.length ? parentMarks : void 0 });
14705
+ out.push({ type: "text", text: stripLeftoverStars(String(tok.text ?? "").replace(/<[^>]+>/g, "")), marks: parentMarks.length ? parentMarks : void 0 });
14703
14706
  break;
14704
14707
  default:
14705
- if (tok.text) out.push({ type: "text", text: String(tok.text), marks: parentMarks.length ? parentMarks : void 0 });
14708
+ if (tok.text) out.push({ type: "text", text: stripLeftoverStars(String(tok.text)), marks: parentMarks.length ? parentMarks : void 0 });
14706
14709
  }
14707
14710
  }
14708
14711
  return out;
@@ -14731,7 +14734,12 @@ function listItemToBlocks(item) {
14731
14734
  const subs = Array.isArray(item?.tokens) ? item.tokens : [];
14732
14735
  const blocks = [];
14733
14736
  for (const sub of subs) {
14734
- const b2 = tokenToAdfBlock(sub);
14737
+ const tok = sub;
14738
+ if (tok?.type === "text" && Array.isArray(tok.tokens)) {
14739
+ blocks.push(paragraphFromTokens(tok.text, tok.tokens));
14740
+ continue;
14741
+ }
14742
+ const b2 = tokenToAdfBlock(tok);
14735
14743
  if (b2) blocks.push(b2);
14736
14744
  }
14737
14745
  if (!blocks.length) {
@@ -14812,9 +14820,12 @@ function markdownToAdf(md) {
14812
14820
  function escapeHtml(s2) {
14813
14821
  return s2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
14814
14822
  }
14823
+ function stripLeftoverStarsFromHtml(html) {
14824
+ return html.split(/(<code>[\s\S]*?<\/code>)/g).map((part, i2) => i2 % 2 === 1 ? part : part.replace(/\*\*/g, "")).join("");
14825
+ }
14815
14826
  function inlineToHtml(text) {
14816
14827
  const html = g.parseInline(text ?? "");
14817
- return (typeof html === "string" ? html : "").replace(/<br\s*\/?>/gi, "<br/>");
14828
+ return stripLeftoverStarsFromHtml(typeof html === "string" ? html : "").replace(/<br\s*\/?>/gi, "<br/>");
14818
14829
  }
14819
14830
  function listItemToHtml(item) {
14820
14831
  const subs = Array.isArray(item?.tokens) ? item.tokens : [];
@@ -31961,6 +31972,7 @@ init_jira_client();
31961
31972
 
31962
31973
  // src/confluence-client.ts
31963
31974
  init_src();
31975
+ var REQUEST_TIMEOUT_MS = 6e4;
31964
31976
  var ConfluenceClient = class {
31965
31977
  baseUrl;
31966
31978
  auth;
@@ -31981,22 +31993,44 @@ var ConfluenceClient = class {
31981
31993
  getBaseUrl() {
31982
31994
  return this.baseUrl;
31983
31995
  }
31996
+ /**
31997
+ * 공통 fetch 래퍼 — timeout(AbortController) + 인증 헤더 + 에러 정규화.
31998
+ * label은 에러 메시지 식별용(get/create/update/...). 빈 응답은 null 반환.
31999
+ * timeout 초과 시 throw(`Confluence <label> timed out after Ns`).
32000
+ */
32001
+ async request(url, init) {
32002
+ const controller = new AbortController();
32003
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
32004
+ try {
32005
+ const resp = await fetch(url, {
32006
+ method: init.method || "GET",
32007
+ headers: { ...this.headers(), Authorization: this.authHeader() },
32008
+ body: init.body,
32009
+ signal: controller.signal
32010
+ });
32011
+ if (!resp.ok) {
32012
+ throw new Error(`Confluence ${init.label} ${resp.status}: ${await resp.text()}`);
32013
+ }
32014
+ const text = await resp.text();
32015
+ return text ? JSON.parse(text) : null;
32016
+ } catch (err) {
32017
+ if (err?.name === "AbortError") {
32018
+ throw new Error(`Confluence ${init.label} timed out after ${REQUEST_TIMEOUT_MS / 1e3}s`);
32019
+ }
32020
+ throw err;
32021
+ } finally {
32022
+ clearTimeout(timer);
32023
+ }
32024
+ }
31984
32025
  async getPage(pageId, expand) {
31985
32026
  const params = expand?.length ? `?expand=${expand.join(",")}` : "";
31986
- const resp = await fetch(
31987
- `${this.baseUrl}/wiki/rest/api/content/${pageId}${params}`,
31988
- { headers: { ...this.headers(), Authorization: this.authHeader() } }
31989
- );
31990
- if (!resp.ok) throw new Error(`Confluence get ${resp.status}: ${await resp.text()}`);
31991
- return resp.json();
32027
+ return this.request(`${this.baseUrl}/wiki/rest/api/content/${pageId}${params}`, { label: "get" });
31992
32028
  }
31993
32029
  async search(cql, limit = 25) {
31994
- const resp = await fetch(
32030
+ return this.request(
31995
32031
  `${this.baseUrl}/wiki/rest/api/content/search?cql=${encodeURIComponent(cql)}&limit=${limit}`,
31996
- { headers: { ...this.headers(), Authorization: this.authHeader() } }
32032
+ { label: "search" }
31997
32033
  );
31998
- if (!resp.ok) throw new Error(`Confluence search ${resp.status}: ${await resp.text()}`);
31999
- return resp.json();
32000
32034
  }
32001
32035
  async createPage(spaceKey, title, body, parentId) {
32002
32036
  const payload = {
@@ -32013,65 +32047,44 @@ var ConfluenceClient = class {
32013
32047
  if (parentId) {
32014
32048
  payload.ancestors = [{ id: parentId }];
32015
32049
  }
32016
- const resp = await fetch(
32017
- `${this.baseUrl}/wiki/rest/api/content`,
32018
- {
32019
- method: "POST",
32020
- headers: { ...this.headers(), Authorization: this.authHeader() },
32021
- body: JSON.stringify(payload)
32022
- }
32023
- );
32024
- if (!resp.ok) throw new Error(`Confluence create ${resp.status}: ${await resp.text()}`);
32025
- return resp.json();
32050
+ return this.request(`${this.baseUrl}/wiki/rest/api/content`, {
32051
+ method: "POST",
32052
+ label: "create",
32053
+ body: JSON.stringify(payload)
32054
+ });
32026
32055
  }
32027
32056
  async updatePage(pageId, title, body, version2) {
32028
- const resp = await fetch(
32029
- `${this.baseUrl}/wiki/rest/api/content/${pageId}`,
32030
- {
32031
- method: "PUT",
32032
- headers: { ...this.headers(), Authorization: this.authHeader() },
32033
- body: JSON.stringify({
32034
- type: "page",
32035
- title,
32036
- version: { number: version2 + 1 },
32037
- body: {
32038
- storage: {
32039
- value: body,
32040
- representation: "storage"
32041
- }
32057
+ return this.request(`${this.baseUrl}/wiki/rest/api/content/${pageId}`, {
32058
+ method: "PUT",
32059
+ label: "update",
32060
+ body: JSON.stringify({
32061
+ type: "page",
32062
+ title,
32063
+ version: { number: version2 + 1 },
32064
+ body: {
32065
+ storage: {
32066
+ value: body,
32067
+ representation: "storage"
32042
32068
  }
32043
- })
32044
- }
32045
- );
32046
- if (!resp.ok) throw new Error(`Confluence update ${resp.status}: ${await resp.text()}`);
32047
- return resp.json();
32069
+ }
32070
+ })
32071
+ });
32048
32072
  }
32049
32073
  async deletePage(pageId) {
32050
- const resp = await fetch(
32051
- `${this.baseUrl}/wiki/rest/api/content/${pageId}`,
32052
- {
32053
- method: "DELETE",
32054
- headers: { ...this.headers(), Authorization: this.authHeader() }
32055
- }
32056
- );
32057
- if (!resp.ok) throw new Error(`Confluence delete ${resp.status}: ${await resp.text()}`);
32074
+ await this.request(`${this.baseUrl}/wiki/rest/api/content/${pageId}`, {
32075
+ method: "DELETE",
32076
+ label: "delete"
32077
+ });
32058
32078
  return { success: true };
32059
32079
  }
32060
32080
  async getSpaces() {
32061
- const resp = await fetch(
32062
- `${this.baseUrl}/wiki/rest/api/space?limit=100`,
32063
- { headers: { ...this.headers(), Authorization: this.authHeader() } }
32064
- );
32065
- if (!resp.ok) throw new Error(`Confluence spaces ${resp.status}: ${await resp.text()}`);
32066
- return resp.json();
32081
+ return this.request(`${this.baseUrl}/wiki/rest/api/space?limit=100`, { label: "spaces" });
32067
32082
  }
32068
32083
  async getPageChildren(pageId, limit = 25) {
32069
- const resp = await fetch(
32084
+ return this.request(
32070
32085
  `${this.baseUrl}/wiki/rest/api/content/${pageId}/child/page?limit=${limit}`,
32071
- { headers: { ...this.headers(), Authorization: this.authHeader() } }
32086
+ { label: "children" }
32072
32087
  );
32073
- if (!resp.ok) throw new Error(`Confluence children ${resp.status}: ${await resp.text()}`);
32074
- return resp.json();
32075
32088
  }
32076
32089
  };
32077
32090
 
@@ -32389,6 +32402,7 @@ var registerJiraCrudTools = (ctx2) => {
32389
32402
 
32390
32403
  // src/tools/confluence.ts
32391
32404
  init_markdown();
32405
+ var STORAGE_BODY_WARN_BYTES = 2 * 1024 * 1024;
32392
32406
  var registerConfluenceTools = (ctx2) => {
32393
32407
  const { server: server2, confluence: confluence2 } = ctx2;
32394
32408
  server2.tool("confluence_get_page", "Get a Confluence page by ID", {
@@ -32448,6 +32462,9 @@ var registerConfluenceTools = (ctx2) => {
32448
32462
  page_id: external_exports.string().optional().describe("Existing page ID. When provided, the page is UPDATED (current version fetched then incremented). When omitted, a NEW page is created.")
32449
32463
  }, async ({ space_key, title, markdown, parent_page_id, page_id }) => {
32450
32464
  const body = markdownToStorageFormat(markdown);
32465
+ const bodyBytes = Buffer.byteLength(body, "utf8");
32466
+ const warning = bodyBytes > STORAGE_BODY_WARN_BYTES ? `storage body is ${bodyBytes} bytes (> ${STORAGE_BODY_WARN_BYTES}). Confluence may reject or render slowly \u2014 consider splitting into child pages.` : void 0;
32467
+ if (warning) console.error(`[confluence_publish_doc] ${warning}`);
32451
32468
  let result;
32452
32469
  let mode;
32453
32470
  if (page_id) {
@@ -32472,7 +32489,9 @@ var registerConfluenceTools = (ctx2) => {
32472
32489
  space_key,
32473
32490
  version: result?.version?.number,
32474
32491
  url,
32475
- body_chars: body.length
32492
+ body_chars: body.length,
32493
+ body_bytes: bodyBytes,
32494
+ ...warning ? { warning } : {}
32476
32495
  }, null, 2)
32477
32496
  }]
32478
32497
  };
@@ -33994,7 +34013,7 @@ var xrayClientSecret = process.env.XRAY_CLIENT_SECRET || "";
33994
34013
  var xray = xrayClientId && xrayClientSecret ? new XrayClient(xrayClientId, xrayClientSecret, jiraUrl, jiraEmail, jiraToken) : null;
33995
34014
  var server = new McpServer({
33996
34015
  name: "jira-confluence",
33997
- version: "0.1.10"
34016
+ version: "0.1.12"
33998
34017
  });
33999
34018
  var ctx = { server, jira, confluence, risk, xray };
34000
34019
  registerJiraCrudTools(ctx);