@neobiotechlabs/neobiotech-dev-agent 0.1.10 → 0.1.11

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.11"
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.11",
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;
@@ -14812,9 +14815,12 @@ function markdownToAdf(md) {
14812
14815
  function escapeHtml(s2) {
14813
14816
  return s2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
14814
14817
  }
14818
+ function stripLeftoverStarsFromHtml(html) {
14819
+ return html.split(/(<code>[\s\S]*?<\/code>)/g).map((part, i2) => i2 % 2 === 1 ? part : part.replace(/\*\*/g, "")).join("");
14820
+ }
14815
14821
  function inlineToHtml(text) {
14816
14822
  const html = g.parseInline(text ?? "");
14817
- return (typeof html === "string" ? html : "").replace(/<br\s*\/?>/gi, "<br/>");
14823
+ return stripLeftoverStarsFromHtml(typeof html === "string" ? html : "").replace(/<br\s*\/?>/gi, "<br/>");
14818
14824
  }
14819
14825
  function listItemToHtml(item) {
14820
14826
  const subs = Array.isArray(item?.tokens) ? item.tokens : [];
@@ -31961,6 +31967,7 @@ init_jira_client();
31961
31967
 
31962
31968
  // src/confluence-client.ts
31963
31969
  init_src();
31970
+ var REQUEST_TIMEOUT_MS = 6e4;
31964
31971
  var ConfluenceClient = class {
31965
31972
  baseUrl;
31966
31973
  auth;
@@ -31981,22 +31988,44 @@ var ConfluenceClient = class {
31981
31988
  getBaseUrl() {
31982
31989
  return this.baseUrl;
31983
31990
  }
31991
+ /**
31992
+ * 공통 fetch 래퍼 — timeout(AbortController) + 인증 헤더 + 에러 정규화.
31993
+ * label은 에러 메시지 식별용(get/create/update/...). 빈 응답은 null 반환.
31994
+ * timeout 초과 시 throw(`Confluence <label> timed out after Ns`).
31995
+ */
31996
+ async request(url, init) {
31997
+ const controller = new AbortController();
31998
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
31999
+ try {
32000
+ const resp = await fetch(url, {
32001
+ method: init.method || "GET",
32002
+ headers: { ...this.headers(), Authorization: this.authHeader() },
32003
+ body: init.body,
32004
+ signal: controller.signal
32005
+ });
32006
+ if (!resp.ok) {
32007
+ throw new Error(`Confluence ${init.label} ${resp.status}: ${await resp.text()}`);
32008
+ }
32009
+ const text = await resp.text();
32010
+ return text ? JSON.parse(text) : null;
32011
+ } catch (err) {
32012
+ if (err?.name === "AbortError") {
32013
+ throw new Error(`Confluence ${init.label} timed out after ${REQUEST_TIMEOUT_MS / 1e3}s`);
32014
+ }
32015
+ throw err;
32016
+ } finally {
32017
+ clearTimeout(timer);
32018
+ }
32019
+ }
31984
32020
  async getPage(pageId, expand) {
31985
32021
  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();
32022
+ return this.request(`${this.baseUrl}/wiki/rest/api/content/${pageId}${params}`, { label: "get" });
31992
32023
  }
31993
32024
  async search(cql, limit = 25) {
31994
- const resp = await fetch(
32025
+ return this.request(
31995
32026
  `${this.baseUrl}/wiki/rest/api/content/search?cql=${encodeURIComponent(cql)}&limit=${limit}`,
31996
- { headers: { ...this.headers(), Authorization: this.authHeader() } }
32027
+ { label: "search" }
31997
32028
  );
31998
- if (!resp.ok) throw new Error(`Confluence search ${resp.status}: ${await resp.text()}`);
31999
- return resp.json();
32000
32029
  }
32001
32030
  async createPage(spaceKey, title, body, parentId) {
32002
32031
  const payload = {
@@ -32013,65 +32042,44 @@ var ConfluenceClient = class {
32013
32042
  if (parentId) {
32014
32043
  payload.ancestors = [{ id: parentId }];
32015
32044
  }
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();
32045
+ return this.request(`${this.baseUrl}/wiki/rest/api/content`, {
32046
+ method: "POST",
32047
+ label: "create",
32048
+ body: JSON.stringify(payload)
32049
+ });
32026
32050
  }
32027
32051
  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
- }
32052
+ return this.request(`${this.baseUrl}/wiki/rest/api/content/${pageId}`, {
32053
+ method: "PUT",
32054
+ label: "update",
32055
+ body: JSON.stringify({
32056
+ type: "page",
32057
+ title,
32058
+ version: { number: version2 + 1 },
32059
+ body: {
32060
+ storage: {
32061
+ value: body,
32062
+ representation: "storage"
32042
32063
  }
32043
- })
32044
- }
32045
- );
32046
- if (!resp.ok) throw new Error(`Confluence update ${resp.status}: ${await resp.text()}`);
32047
- return resp.json();
32064
+ }
32065
+ })
32066
+ });
32048
32067
  }
32049
32068
  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()}`);
32069
+ await this.request(`${this.baseUrl}/wiki/rest/api/content/${pageId}`, {
32070
+ method: "DELETE",
32071
+ label: "delete"
32072
+ });
32058
32073
  return { success: true };
32059
32074
  }
32060
32075
  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();
32076
+ return this.request(`${this.baseUrl}/wiki/rest/api/space?limit=100`, { label: "spaces" });
32067
32077
  }
32068
32078
  async getPageChildren(pageId, limit = 25) {
32069
- const resp = await fetch(
32079
+ return this.request(
32070
32080
  `${this.baseUrl}/wiki/rest/api/content/${pageId}/child/page?limit=${limit}`,
32071
- { headers: { ...this.headers(), Authorization: this.authHeader() } }
32081
+ { label: "children" }
32072
32082
  );
32073
- if (!resp.ok) throw new Error(`Confluence children ${resp.status}: ${await resp.text()}`);
32074
- return resp.json();
32075
32083
  }
32076
32084
  };
32077
32085
 
@@ -32389,6 +32397,7 @@ var registerJiraCrudTools = (ctx2) => {
32389
32397
 
32390
32398
  // src/tools/confluence.ts
32391
32399
  init_markdown();
32400
+ var STORAGE_BODY_WARN_BYTES = 2 * 1024 * 1024;
32392
32401
  var registerConfluenceTools = (ctx2) => {
32393
32402
  const { server: server2, confluence: confluence2 } = ctx2;
32394
32403
  server2.tool("confluence_get_page", "Get a Confluence page by ID", {
@@ -32448,6 +32457,9 @@ var registerConfluenceTools = (ctx2) => {
32448
32457
  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
32458
  }, async ({ space_key, title, markdown, parent_page_id, page_id }) => {
32450
32459
  const body = markdownToStorageFormat(markdown);
32460
+ const bodyBytes = Buffer.byteLength(body, "utf8");
32461
+ 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;
32462
+ if (warning) console.error(`[confluence_publish_doc] ${warning}`);
32451
32463
  let result;
32452
32464
  let mode;
32453
32465
  if (page_id) {
@@ -32472,7 +32484,9 @@ var registerConfluenceTools = (ctx2) => {
32472
32484
  space_key,
32473
32485
  version: result?.version?.number,
32474
32486
  url,
32475
- body_chars: body.length
32487
+ body_chars: body.length,
32488
+ body_bytes: bodyBytes,
32489
+ ...warning ? { warning } : {}
32476
32490
  }, null, 2)
32477
32491
  }]
32478
32492
  };
@@ -33994,7 +34008,7 @@ var xrayClientSecret = process.env.XRAY_CLIENT_SECRET || "";
33994
34008
  var xray = xrayClientId && xrayClientSecret ? new XrayClient(xrayClientId, xrayClientSecret, jiraUrl, jiraEmail, jiraToken) : null;
33995
34009
  var server = new McpServer({
33996
34010
  name: "jira-confluence",
33997
- version: "0.1.10"
34011
+ version: "0.1.11"
33998
34012
  });
33999
34013
  var ctx = { server, jira, confluence, risk, xray };
34000
34014
  registerJiraCrudTools(ctx);