@neobiotechlabs/neobiotech-dev-agent 0.1.27 → 0.1.29

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.js CHANGED
@@ -2995,7 +2995,7 @@ var require_compile = __commonJS({
2995
2995
  const schOrFunc = root.refs[ref];
2996
2996
  if (schOrFunc)
2997
2997
  return schOrFunc;
2998
- let _sch = resolve.call(this, root, ref);
2998
+ let _sch = resolve2.call(this, root, ref);
2999
2999
  if (_sch === void 0) {
3000
3000
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
3001
3001
  const { schemaId } = this.opts;
@@ -3022,7 +3022,7 @@ var require_compile = __commonJS({
3022
3022
  function sameSchemaEnv(s1, s2) {
3023
3023
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3024
3024
  }
3025
- function resolve(root, ref) {
3025
+ function resolve2(root, ref) {
3026
3026
  let sch;
3027
3027
  while (typeof (sch = this.refs[ref]) == "string")
3028
3028
  ref = sch;
@@ -3653,7 +3653,7 @@ var require_fast_uri = __commonJS({
3653
3653
  }
3654
3654
  return uri;
3655
3655
  }
3656
- function resolve(baseURI, relativeURI, options) {
3656
+ function resolve2(baseURI, relativeURI, options) {
3657
3657
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3658
3658
  const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
3659
3659
  schemelessOptions.skipEscape = true;
@@ -3917,7 +3917,7 @@ var require_fast_uri = __commonJS({
3917
3917
  var fastUri = {
3918
3918
  SCHEMES,
3919
3919
  normalize,
3920
- resolve,
3920
+ resolve: resolve2,
3921
3921
  resolveComponent,
3922
3922
  equal,
3923
3923
  serialize,
@@ -6982,7 +6982,7 @@ var require_ponyfill_es2018 = __commonJS({
6982
6982
  return new originalPromise(executor);
6983
6983
  }
6984
6984
  function promiseResolvedWith(value) {
6985
- return newPromise((resolve) => resolve(value));
6985
+ return newPromise((resolve2) => resolve2(value));
6986
6986
  }
6987
6987
  function promiseRejectedWith(reason) {
6988
6988
  return originalPromiseReject(reason);
@@ -7152,8 +7152,8 @@ var require_ponyfill_es2018 = __commonJS({
7152
7152
  return new TypeError("Cannot " + name + " a stream using a released reader");
7153
7153
  }
7154
7154
  function defaultReaderClosedPromiseInitialize(reader) {
7155
- reader._closedPromise = newPromise((resolve, reject) => {
7156
- reader._closedPromise_resolve = resolve;
7155
+ reader._closedPromise = newPromise((resolve2, reject) => {
7156
+ reader._closedPromise_resolve = resolve2;
7157
7157
  reader._closedPromise_reject = reject;
7158
7158
  });
7159
7159
  }
@@ -7327,8 +7327,8 @@ var require_ponyfill_es2018 = __commonJS({
7327
7327
  }
7328
7328
  let resolvePromise;
7329
7329
  let rejectPromise;
7330
- const promise = newPromise((resolve, reject) => {
7331
- resolvePromise = resolve;
7330
+ const promise = newPromise((resolve2, reject) => {
7331
+ resolvePromise = resolve2;
7332
7332
  rejectPromise = reject;
7333
7333
  });
7334
7334
  const readRequest = {
@@ -7433,8 +7433,8 @@ var require_ponyfill_es2018 = __commonJS({
7433
7433
  const reader = this._reader;
7434
7434
  let resolvePromise;
7435
7435
  let rejectPromise;
7436
- const promise = newPromise((resolve, reject) => {
7437
- resolvePromise = resolve;
7436
+ const promise = newPromise((resolve2, reject) => {
7437
+ resolvePromise = resolve2;
7438
7438
  rejectPromise = reject;
7439
7439
  });
7440
7440
  const readRequest = {
@@ -8453,8 +8453,8 @@ var require_ponyfill_es2018 = __commonJS({
8453
8453
  }
8454
8454
  let resolvePromise;
8455
8455
  let rejectPromise;
8456
- const promise = newPromise((resolve, reject) => {
8457
- resolvePromise = resolve;
8456
+ const promise = newPromise((resolve2, reject) => {
8457
+ resolvePromise = resolve2;
8458
8458
  rejectPromise = reject;
8459
8459
  });
8460
8460
  const readIntoRequest = {
@@ -8766,10 +8766,10 @@ var require_ponyfill_es2018 = __commonJS({
8766
8766
  wasAlreadyErroring = true;
8767
8767
  reason = void 0;
8768
8768
  }
8769
- const promise = newPromise((resolve, reject) => {
8769
+ const promise = newPromise((resolve2, reject) => {
8770
8770
  stream2._pendingAbortRequest = {
8771
8771
  _promise: void 0,
8772
- _resolve: resolve,
8772
+ _resolve: resolve2,
8773
8773
  _reject: reject,
8774
8774
  _reason: reason,
8775
8775
  _wasAlreadyErroring: wasAlreadyErroring
@@ -8786,9 +8786,9 @@ var require_ponyfill_es2018 = __commonJS({
8786
8786
  if (state === "closed" || state === "errored") {
8787
8787
  return promiseRejectedWith(new TypeError(`The stream (in ${state} state) is not in the writable state and cannot be closed`));
8788
8788
  }
8789
- const promise = newPromise((resolve, reject) => {
8789
+ const promise = newPromise((resolve2, reject) => {
8790
8790
  const closeRequest = {
8791
- _resolve: resolve,
8791
+ _resolve: resolve2,
8792
8792
  _reject: reject
8793
8793
  };
8794
8794
  stream2._closeRequest = closeRequest;
@@ -8801,9 +8801,9 @@ var require_ponyfill_es2018 = __commonJS({
8801
8801
  return promise;
8802
8802
  }
8803
8803
  function WritableStreamAddWriteRequest(stream2) {
8804
- const promise = newPromise((resolve, reject) => {
8804
+ const promise = newPromise((resolve2, reject) => {
8805
8805
  const writeRequest = {
8806
- _resolve: resolve,
8806
+ _resolve: resolve2,
8807
8807
  _reject: reject
8808
8808
  };
8809
8809
  stream2._writeRequests.push(writeRequest);
@@ -9419,8 +9419,8 @@ var require_ponyfill_es2018 = __commonJS({
9419
9419
  return new TypeError("Cannot " + name + " a stream using a released writer");
9420
9420
  }
9421
9421
  function defaultWriterClosedPromiseInitialize(writer) {
9422
- writer._closedPromise = newPromise((resolve, reject) => {
9423
- writer._closedPromise_resolve = resolve;
9422
+ writer._closedPromise = newPromise((resolve2, reject) => {
9423
+ writer._closedPromise_resolve = resolve2;
9424
9424
  writer._closedPromise_reject = reject;
9425
9425
  writer._closedPromiseState = "pending";
9426
9426
  });
@@ -9456,8 +9456,8 @@ var require_ponyfill_es2018 = __commonJS({
9456
9456
  writer._closedPromiseState = "resolved";
9457
9457
  }
9458
9458
  function defaultWriterReadyPromiseInitialize(writer) {
9459
- writer._readyPromise = newPromise((resolve, reject) => {
9460
- writer._readyPromise_resolve = resolve;
9459
+ writer._readyPromise = newPromise((resolve2, reject) => {
9460
+ writer._readyPromise_resolve = resolve2;
9461
9461
  writer._readyPromise_reject = reject;
9462
9462
  });
9463
9463
  writer._readyPromiseState = "pending";
@@ -9544,7 +9544,7 @@ var require_ponyfill_es2018 = __commonJS({
9544
9544
  source._disturbed = true;
9545
9545
  let shuttingDown = false;
9546
9546
  let currentWrite = promiseResolvedWith(void 0);
9547
- return newPromise((resolve, reject) => {
9547
+ return newPromise((resolve2, reject) => {
9548
9548
  let abortAlgorithm;
9549
9549
  if (signal !== void 0) {
9550
9550
  abortAlgorithm = () => {
@@ -9689,7 +9689,7 @@ var require_ponyfill_es2018 = __commonJS({
9689
9689
  if (isError) {
9690
9690
  reject(error2);
9691
9691
  } else {
9692
- resolve(void 0);
9692
+ resolve2(void 0);
9693
9693
  }
9694
9694
  return null;
9695
9695
  }
@@ -9970,8 +9970,8 @@ var require_ponyfill_es2018 = __commonJS({
9970
9970
  let branch1;
9971
9971
  let branch2;
9972
9972
  let resolveCancelPromise;
9973
- const cancelPromise = newPromise((resolve) => {
9974
- resolveCancelPromise = resolve;
9973
+ const cancelPromise = newPromise((resolve2) => {
9974
+ resolveCancelPromise = resolve2;
9975
9975
  });
9976
9976
  function pullAlgorithm() {
9977
9977
  if (reading) {
@@ -10062,8 +10062,8 @@ var require_ponyfill_es2018 = __commonJS({
10062
10062
  let branch1;
10063
10063
  let branch2;
10064
10064
  let resolveCancelPromise;
10065
- const cancelPromise = newPromise((resolve) => {
10066
- resolveCancelPromise = resolve;
10065
+ const cancelPromise = newPromise((resolve2) => {
10066
+ resolveCancelPromise = resolve2;
10067
10067
  });
10068
10068
  function forwardReaderError(thisReader) {
10069
10069
  uponRejection(thisReader._closedPromise, (r2) => {
@@ -10843,8 +10843,8 @@ var require_ponyfill_es2018 = __commonJS({
10843
10843
  const writableHighWaterMark = ExtractHighWaterMark(writableStrategy, 1);
10844
10844
  const writableSizeAlgorithm = ExtractSizeAlgorithm(writableStrategy);
10845
10845
  let startPromise_resolve;
10846
- const startPromise = newPromise((resolve) => {
10847
- startPromise_resolve = resolve;
10846
+ const startPromise = newPromise((resolve2) => {
10847
+ startPromise_resolve = resolve2;
10848
10848
  });
10849
10849
  InitializeTransformStream(this, startPromise, writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm);
10850
10850
  SetUpTransformStreamDefaultControllerFromTransformer(this, transformer);
@@ -10937,8 +10937,8 @@ var require_ponyfill_es2018 = __commonJS({
10937
10937
  if (stream2._backpressureChangePromise !== void 0) {
10938
10938
  stream2._backpressureChangePromise_resolve();
10939
10939
  }
10940
- stream2._backpressureChangePromise = newPromise((resolve) => {
10941
- stream2._backpressureChangePromise_resolve = resolve;
10940
+ stream2._backpressureChangePromise = newPromise((resolve2) => {
10941
+ stream2._backpressureChangePromise_resolve = resolve2;
10942
10942
  });
10943
10943
  stream2._backpressure = backpressure;
10944
10944
  }
@@ -11106,8 +11106,8 @@ var require_ponyfill_es2018 = __commonJS({
11106
11106
  return controller._finishPromise;
11107
11107
  }
11108
11108
  const readable = stream2._readable;
11109
- controller._finishPromise = newPromise((resolve, reject) => {
11110
- controller._finishPromise_resolve = resolve;
11109
+ controller._finishPromise = newPromise((resolve2, reject) => {
11110
+ controller._finishPromise_resolve = resolve2;
11111
11111
  controller._finishPromise_reject = reject;
11112
11112
  });
11113
11113
  const cancelPromise = controller._cancelAlgorithm(reason);
@@ -11133,8 +11133,8 @@ var require_ponyfill_es2018 = __commonJS({
11133
11133
  return controller._finishPromise;
11134
11134
  }
11135
11135
  const readable = stream2._readable;
11136
- controller._finishPromise = newPromise((resolve, reject) => {
11137
- controller._finishPromise_resolve = resolve;
11136
+ controller._finishPromise = newPromise((resolve2, reject) => {
11137
+ controller._finishPromise_resolve = resolve2;
11138
11138
  controller._finishPromise_reject = reject;
11139
11139
  });
11140
11140
  const flushPromise = controller._flushAlgorithm();
@@ -11164,8 +11164,8 @@ var require_ponyfill_es2018 = __commonJS({
11164
11164
  return controller._finishPromise;
11165
11165
  }
11166
11166
  const writable = stream2._writable;
11167
- controller._finishPromise = newPromise((resolve, reject) => {
11168
- controller._finishPromise_resolve = resolve;
11167
+ controller._finishPromise = newPromise((resolve2, reject) => {
11168
+ controller._finishPromise_resolve = resolve2;
11169
11169
  controller._finishPromise_reject = reject;
11170
11170
  });
11171
11171
  const cancelPromise = controller._cancelAlgorithm(reason);
@@ -13066,7 +13066,7 @@ import zlib from "zlib";
13066
13066
  import Stream2, { PassThrough as PassThrough2, pipeline as pump } from "stream";
13067
13067
  import { Buffer as Buffer3 } from "buffer";
13068
13068
  async function fetch(url, options_) {
13069
- return new Promise((resolve, reject) => {
13069
+ return new Promise((resolve2, reject) => {
13070
13070
  const request = new Request(url, options_);
13071
13071
  const { parsedURL, options } = getNodeRequestOptions(request);
13072
13072
  if (!supportedSchemas.has(parsedURL.protocol)) {
@@ -13075,7 +13075,7 @@ async function fetch(url, options_) {
13075
13075
  if (parsedURL.protocol === "data:") {
13076
13076
  const data = dist_default(request.url);
13077
13077
  const response2 = new Response(data, { headers: { "Content-Type": data.typeFull } });
13078
- resolve(response2);
13078
+ resolve2(response2);
13079
13079
  return;
13080
13080
  }
13081
13081
  const send = (parsedURL.protocol === "https:" ? https : http2).request;
@@ -13197,7 +13197,7 @@ async function fetch(url, options_) {
13197
13197
  if (responseReferrerPolicy) {
13198
13198
  requestOptions.referrerPolicy = responseReferrerPolicy;
13199
13199
  }
13200
- resolve(fetch(new Request(locationURL, requestOptions)));
13200
+ resolve2(fetch(new Request(locationURL, requestOptions)));
13201
13201
  finalize();
13202
13202
  return;
13203
13203
  }
@@ -13230,7 +13230,7 @@ async function fetch(url, options_) {
13230
13230
  const codings = headers.get("Content-Encoding");
13231
13231
  if (!request.compress || request.method === "HEAD" || codings === null || response_.statusCode === 204 || response_.statusCode === 304) {
13232
13232
  response = new Response(body, responseOptions);
13233
- resolve(response);
13233
+ resolve2(response);
13234
13234
  return;
13235
13235
  }
13236
13236
  const zlibOptions = {
@@ -13244,7 +13244,7 @@ async function fetch(url, options_) {
13244
13244
  }
13245
13245
  });
13246
13246
  response = new Response(body, responseOptions);
13247
- resolve(response);
13247
+ resolve2(response);
13248
13248
  return;
13249
13249
  }
13250
13250
  if (codings === "deflate" || codings === "x-deflate") {
@@ -13268,12 +13268,12 @@ async function fetch(url, options_) {
13268
13268
  });
13269
13269
  }
13270
13270
  response = new Response(body, responseOptions);
13271
- resolve(response);
13271
+ resolve2(response);
13272
13272
  });
13273
13273
  raw.once("end", () => {
13274
13274
  if (!response) {
13275
13275
  response = new Response(body, responseOptions);
13276
- resolve(response);
13276
+ resolve2(response);
13277
13277
  }
13278
13278
  });
13279
13279
  return;
@@ -13285,11 +13285,11 @@ async function fetch(url, options_) {
13285
13285
  }
13286
13286
  });
13287
13287
  response = new Response(body, responseOptions);
13288
- resolve(response);
13288
+ resolve2(response);
13289
13289
  return;
13290
13290
  }
13291
13291
  response = new Response(body, responseOptions);
13292
- resolve(response);
13292
+ resolve2(response);
13293
13293
  });
13294
13294
  writeToStream(request_, request).catch(reject);
13295
13295
  });
@@ -14845,7 +14845,7 @@ function listItemToHtml(item) {
14845
14845
  if (paras.length) return paras.map((p) => inlineToHtml(p.text)).join("<br/>");
14846
14846
  return inlineToHtml(item?.text);
14847
14847
  }
14848
- function tokenToStorageHtml(tok) {
14848
+ function tokenToStorageHtml(tok, nextPumlId, pumlRegistry) {
14849
14849
  if (!tok || typeof tok !== "object") return "";
14850
14850
  switch (tok.type) {
14851
14851
  case "heading":
@@ -14853,6 +14853,12 @@ function tokenToStorageHtml(tok) {
14853
14853
  case "paragraph":
14854
14854
  return `<p>${inlineToHtml(tok.text)}</p>`;
14855
14855
  case "code": {
14856
+ const lang = String(tok.lang ?? "").trim().toLowerCase();
14857
+ if (lang === "plantuml" || lang === "puml" || lang === "plantuml-markdown") {
14858
+ const id = nextPumlId();
14859
+ pumlRegistry.set(id, String(tok.text ?? ""));
14860
+ return `<p data-puml-id="${id}"></p>`;
14861
+ }
14856
14862
  const langParam = tok.lang ? `<ac:parameter ac:name="language">${escapeHtml(String(tok.lang))}</ac:parameter>` : "";
14857
14863
  return `<ac:structured-macro ac:name="code">${langParam}<ac:plain-text-body><![CDATA[${tok.text ?? ""}]]></ac:plain-text-body></ac:structured-macro>`;
14858
14864
  }
@@ -14862,7 +14868,7 @@ function tokenToStorageHtml(tok) {
14862
14868
  return `<${tag}>${items}</${tag}>`;
14863
14869
  }
14864
14870
  case "blockquote":
14865
- return `<blockquote>${(tok.tokens || []).map((t2) => tokenToStorageHtml(t2)).join("")}</blockquote>`;
14871
+ return `<blockquote>${(tok.tokens || []).map((t2) => tokenToStorageHtml(t2, nextPumlId, pumlRegistry)).join("")}</blockquote>`;
14866
14872
  case "table": {
14867
14873
  const header = (tok.header || []).map((c) => `<th>${inlineToHtml(c.text)}</th>`).join("");
14868
14874
  const rows = (tok.rows || []).map(
@@ -14878,12 +14884,17 @@ function tokenToStorageHtml(tok) {
14878
14884
  }
14879
14885
  function markdownToStorageFormat(md) {
14880
14886
  const tokens = g.lexer(stripFrontmatter(md ?? ""));
14887
+ const pumlRegistry = /* @__PURE__ */ new Map();
14881
14888
  const parts = [];
14889
+ let pumlCounter = 0;
14882
14890
  for (const tok of tokens) {
14883
- const html = tokenToStorageHtml(tok);
14891
+ const html = tokenToStorageHtml(tok, () => `PUML_${pumlCounter++}`, pumlRegistry);
14884
14892
  if (html) parts.push(html);
14885
14893
  }
14886
- return parts.join("\n") || "<p></p>";
14894
+ return {
14895
+ storage: parts.join("\n") || "<p></p>",
14896
+ pumlRegistry
14897
+ };
14887
14898
  }
14888
14899
  var FRONTMATTER_RE;
14889
14900
  var init_markdown = __esm({
@@ -17106,8 +17117,8 @@ var require_adm_zip = __commonJS({
17106
17117
  return null;
17107
17118
  }
17108
17119
  function fixPath(zipPath) {
17109
- const { join: join5, normalize, sep } = pth.posix;
17110
- return join5(pth.isAbsolute(zipPath) ? "/" : ".", normalize(sep + zipPath.split("\\").join(sep) + sep));
17120
+ const { join: join6, normalize, sep } = pth.posix;
17121
+ return join6(pth.isAbsolute(zipPath) ? "/" : ".", normalize(sep + zipPath.split("\\").join(sep) + sep));
17111
17122
  }
17112
17123
  function filenameFilter(filterfn) {
17113
17124
  if (filterfn instanceof RegExp) {
@@ -17503,10 +17514,10 @@ var require_adm_zip = __commonJS({
17503
17514
  * @param {function|string} [props.namefix] - optional function to help fix filename
17504
17515
  */
17505
17516
  addLocalFolderPromise: function(localPath, props) {
17506
- return new Promise((resolve, reject) => {
17517
+ return new Promise((resolve2, reject) => {
17507
17518
  this.addLocalFolderAsync2(Object.assign({ localPath }, props), (err, done) => {
17508
17519
  if (err) reject(err);
17509
- if (done) resolve(this);
17520
+ if (done) resolve2(this);
17510
17521
  });
17511
17522
  });
17512
17523
  },
@@ -17693,12 +17704,12 @@ var require_adm_zip = __commonJS({
17693
17704
  keepOriginalPermission = get_Bool(false, keepOriginalPermission);
17694
17705
  overwrite = get_Bool(false, overwrite);
17695
17706
  if (!callback) {
17696
- return new Promise((resolve, reject) => {
17707
+ return new Promise((resolve2, reject) => {
17697
17708
  this.extractAllToAsync(targetPath, overwrite, keepOriginalPermission, function(err) {
17698
17709
  if (err) {
17699
17710
  reject(err);
17700
17711
  } else {
17701
- resolve(this);
17712
+ resolve2(this);
17702
17713
  }
17703
17714
  });
17704
17715
  });
@@ -17796,11 +17807,11 @@ var require_adm_zip = __commonJS({
17796
17807
  */
17797
17808
  writeZipPromise: function(targetFileName, props) {
17798
17809
  const { overwrite, perm } = Object.assign({ overwrite: true }, props);
17799
- return new Promise((resolve, reject) => {
17810
+ return new Promise((resolve2, reject) => {
17800
17811
  if (!targetFileName && opts.filename) targetFileName = opts.filename;
17801
17812
  if (!targetFileName) reject("ADM-ZIP: ZIP File Name Missing");
17802
17813
  this.toBufferPromise().then((zipData) => {
17803
- const ret = (done) => done ? resolve(done) : reject("ADM-ZIP: Wasn't able to write zip file");
17814
+ const ret = (done) => done ? resolve2(done) : reject("ADM-ZIP: Wasn't able to write zip file");
17804
17815
  filetools.writeFileToAsync(targetFileName, zipData, overwrite, perm, ret);
17805
17816
  }, reject);
17806
17817
  });
@@ -17809,8 +17820,8 @@ var require_adm_zip = __commonJS({
17809
17820
  * @returns {Promise<Buffer>} A promise to the Buffer.
17810
17821
  */
17811
17822
  toBufferPromise: function() {
17812
- return new Promise((resolve, reject) => {
17813
- _zip.toAsyncBuffer(resolve, reject);
17823
+ return new Promise((resolve2, reject) => {
17824
+ _zip.toAsyncBuffer(resolve2, reject);
17814
17825
  });
17815
17826
  },
17816
17827
  /**
@@ -18949,9 +18960,9 @@ var require_clone = __commonJS({
18949
18960
  } else if (_instanceof(parent2, nativeSet)) {
18950
18961
  child = new nativeSet();
18951
18962
  } else if (_instanceof(parent2, nativePromise)) {
18952
- child = new nativePromise(function(resolve, reject) {
18963
+ child = new nativePromise(function(resolve2, reject) {
18953
18964
  parent2.then(function(value) {
18954
- resolve(_clone(value, depth2 - 1));
18965
+ resolve2(_clone(value, depth2 - 1));
18955
18966
  }, function(err) {
18956
18967
  reject(_clone(err, depth2 - 1));
18957
18968
  });
@@ -157405,7 +157416,7 @@ var Protocol = class {
157405
157416
  return;
157406
157417
  }
157407
157418
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
157408
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
157419
+ await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
157409
157420
  options?.signal?.throwIfAborted();
157410
157421
  }
157411
157422
  } catch (error2) {
@@ -157422,7 +157433,7 @@ var Protocol = class {
157422
157433
  */
157423
157434
  request(request, resultSchema, options) {
157424
157435
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
157425
- return new Promise((resolve, reject) => {
157436
+ return new Promise((resolve2, reject) => {
157426
157437
  const earlyReject = (error2) => {
157427
157438
  reject(error2);
157428
157439
  };
@@ -157500,7 +157511,7 @@ var Protocol = class {
157500
157511
  if (!parseResult.success) {
157501
157512
  reject(parseResult.error);
157502
157513
  } else {
157503
- resolve(parseResult.data);
157514
+ resolve2(parseResult.data);
157504
157515
  }
157505
157516
  } catch (error2) {
157506
157517
  reject(error2);
@@ -157761,12 +157772,12 @@ var Protocol = class {
157761
157772
  }
157762
157773
  } catch {
157763
157774
  }
157764
- return new Promise((resolve, reject) => {
157775
+ return new Promise((resolve2, reject) => {
157765
157776
  if (signal.aborted) {
157766
157777
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
157767
157778
  return;
157768
157779
  }
157769
- const timeoutId = setTimeout(resolve, interval);
157780
+ const timeoutId = setTimeout(resolve2, interval);
157770
157781
  signal.addEventListener("abort", () => {
157771
157782
  clearTimeout(timeoutId);
157772
157783
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -158857,7 +158868,7 @@ var McpServer = class {
158857
158868
  let task = createTaskResult.task;
158858
158869
  const pollInterval = task.pollInterval ?? 5e3;
158859
158870
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
158860
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
158871
+ await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
158861
158872
  const updatedTask = await extra.taskStore.getTask(taskId);
158862
158873
  if (!updatedTask) {
158863
158874
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -159521,12 +159532,12 @@ var StdioServerTransport = class {
159521
159532
  this.onclose?.();
159522
159533
  }
159523
159534
  send(message) {
159524
- return new Promise((resolve) => {
159535
+ return new Promise((resolve2) => {
159525
159536
  const json = serializeMessage(message);
159526
159537
  if (this._stdout.write(json)) {
159527
- resolve();
159538
+ resolve2();
159528
159539
  } else {
159529
- this._stdout.once("drain", resolve);
159540
+ this._stdout.once("drain", resolve2);
159530
159541
  }
159531
159542
  });
159532
159543
  }
@@ -159651,7 +159662,83 @@ var ConfluenceClient = class {
159651
159662
  { label: "children" }
159652
159663
  );
159653
159664
  }
159665
+ /**
159666
+ * 페이지 첨부 업로드 (POST .../content/{pageId}/child/attachment).
159667
+ *
159668
+ * JSON 본문 기반 `request()`와 달리 multipart/form-data 자체 구성을 직접 한다.
159669
+ * - 헤더 `X-Atlassian-Token: no-check`는 첨부 시 **필수** (누락 시 403/404 — CSRF 차단).
159670
+ * - 자체 AbortController 60s timeout (attach만 — request() 미변경).
159671
+ * - Content-Type은 boundary 포함해서 직접 설정. node-fetch가 boundary 자동 계산 안 함.
159672
+ * - 응답은 `{ results: [{ id, title, extensions: { fileSize, mediaType }, _links: { content } }] }`
159673
+ * (Jira와 달리 `results` 래퍼 한 겹). 정규화하여 단일 객체로 반환.
159674
+ *
159675
+ * 가드(50MB warning / 100MB hard)는 호출 툴 핸들러에서 처리 — 본 메서드는 순수 클라이언트.
159676
+ */
159677
+ async addAttachment(pageId, filename, content, mimeType = "application/octet-stream") {
159678
+ const ATTACH_TIMEOUT_MS = 6e4;
159679
+ const boundary = `----confluence-mcp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
159680
+ const safeFilename = escapeMimeFilename2(filename);
159681
+ const fileBuf = Buffer.isBuffer(content) ? content : Buffer.from(content);
159682
+ const head = `--${boundary}\r
159683
+ Content-Disposition: form-data; name="file"; filename="${safeFilename}"\r
159684
+ Content-Type: ${mimeType}\r
159685
+ \r
159686
+ `;
159687
+ const tail = `\r
159688
+ --${boundary}\r
159689
+ Content-Disposition: form-data; name="minorEdit"\r
159690
+ \r
159691
+ true\r
159692
+ --${boundary}--\r
159693
+ `;
159694
+ const headBuf = Buffer.from(head, "utf8");
159695
+ const tailBuf = Buffer.from(tail, "utf8");
159696
+ const body = Buffer.concat([headBuf, fileBuf, tailBuf]);
159697
+ const controller = new AbortController();
159698
+ const timer = setTimeout(() => controller.abort(), ATTACH_TIMEOUT_MS);
159699
+ try {
159700
+ const resp = await fetch(`${this.baseUrl}/wiki/rest/api/content/${encodeURIComponent(pageId)}/child/attachment`, {
159701
+ method: "POST",
159702
+ headers: {
159703
+ "Accept": "application/json",
159704
+ // Content-Type은 body boundary와 정확히 일치해야 함. node-fetch가 자동 설정하지 않음.
159705
+ "Content-Type": `multipart/form-data; boundary=${boundary}`,
159706
+ "Authorization": this.authHeader(),
159707
+ // 첨부 시 필수 헤더. 없으면 Atlassian CSRF 가드가 403/404 응답.
159708
+ "X-Atlassian-Token": "no-check"
159709
+ },
159710
+ body,
159711
+ signal: controller.signal
159712
+ });
159713
+ if (!resp.ok) {
159714
+ const text = await resp.text();
159715
+ throw new Error(`Confluence attachment API ${resp.status}: ${text}`);
159716
+ }
159717
+ const data = await resp.json();
159718
+ const item = Array.isArray(data?.results) ? data.results[0] : data;
159719
+ if (!item?.id) {
159720
+ throw new Error(`Confluence attachment API: missing id in response`);
159721
+ }
159722
+ return {
159723
+ id: String(item.id),
159724
+ title: String(item.title ?? filename),
159725
+ size: Number(item.extensions?.fileSize ?? fileBuf.length),
159726
+ mimeType: String(item.extensions?.mediaType ?? mimeType),
159727
+ content: String(item._links?.content ?? "")
159728
+ };
159729
+ } catch (err) {
159730
+ if (err?.name === "AbortError") {
159731
+ throw new Error(`Confluence attachment timed out after ${ATTACH_TIMEOUT_MS / 1e3}s`);
159732
+ }
159733
+ throw err;
159734
+ } finally {
159735
+ clearTimeout(timer);
159736
+ }
159737
+ }
159654
159738
  };
159739
+ function escapeMimeFilename2(name) {
159740
+ return name.replace(/"/g, "%22").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
159741
+ }
159655
159742
 
159656
159743
  // src/clients/risk-client.ts
159657
159744
  init_src();
@@ -160007,6 +160094,106 @@ var registerJiraCrudTools = (ctx2) => {
160007
160094
  // src/tools/confluence.ts
160008
160095
  init_markdown();
160009
160096
  import { readFileSync } from "fs";
160097
+
160098
+ // src/utils/plantuml-renderer.ts
160099
+ import { spawn } from "child_process";
160100
+ import { createHash } from "crypto";
160101
+ import { readdirSync as readdirSync2, existsSync } from "fs";
160102
+ import { resolve, dirname, join as join2 } from "path";
160103
+ import { fileURLToPath } from "url";
160104
+ var PNG_CT = "image/png";
160105
+ var SVG_CT = "image/svg+xml";
160106
+ var DEFAULT_TIMEOUT_MS = 3e4;
160107
+ var DEFAULT_DPI = 96;
160108
+ var DEFAULT_FORMAT = "png";
160109
+ var JAR_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "build");
160110
+ function resolveJar() {
160111
+ if (!existsSync(JAR_DIR)) {
160112
+ throw new Error(
160113
+ `PlantUML jar directory not found at ${JAR_DIR}. Run 'npm run build' to copy the jar.`
160114
+ );
160115
+ }
160116
+ const jars = readdirSync2(JAR_DIR).filter((f3) => f3.endsWith(".jar"));
160117
+ if (jars.length === 0) {
160118
+ throw new Error(`No .jar files in ${JAR_DIR}. Run 'npm run build' to copy the jar.`);
160119
+ }
160120
+ if (jars.length > 1) {
160121
+ throw new Error(`Multiple .jar files in ${JAR_DIR}: ${jars.join(", ")}. Expected exactly one.`);
160122
+ }
160123
+ return join2(JAR_DIR, jars[0]);
160124
+ }
160125
+ var cache = /* @__PURE__ */ new Map();
160126
+ function makeCacheKey(source, format, dpi) {
160127
+ return createHash("sha256").update(`${source}|${format}|${dpi}`).digest("hex");
160128
+ }
160129
+ function isJavaMissing(err) {
160130
+ if (err?.code !== "ENOENT") return false;
160131
+ const haystack = `${err?.syscall ?? ""} ${err?.path ?? ""} ${JSON.stringify(err?.spawnargs ?? [])}`;
160132
+ return /java/i.test(haystack);
160133
+ }
160134
+ function spawnPlantUml(jarPath, source, format, dpi) {
160135
+ return new Promise((resolveP, rejectP) => {
160136
+ const args = ["-jar", jarPath, "-pipe", `-t${format}`, "-charset", "UTF-8"];
160137
+ if (dpi !== DEFAULT_DPI) {
160138
+ args.push(`-Sdpi=${dpi}`);
160139
+ }
160140
+ const proc = spawn("java", args, { stdio: ["pipe", "pipe", "pipe"] });
160141
+ const chunks = [];
160142
+ proc.stdout.on("data", (c) => chunks.push(c));
160143
+ proc.stderr.on("data", () => {
160144
+ });
160145
+ proc.on("error", (err) => rejectP(err));
160146
+ proc.on("close", (code) => {
160147
+ if (code === 0) {
160148
+ resolveP(Buffer.concat(chunks));
160149
+ } else {
160150
+ rejectP(new Error(`PlantUML jar exited with code ${code}`));
160151
+ }
160152
+ });
160153
+ proc.stdin.write(source);
160154
+ proc.stdin.end();
160155
+ });
160156
+ }
160157
+ async function renderPlantUml(source, opts = {}) {
160158
+ const format = opts.format ?? DEFAULT_FORMAT;
160159
+ const dpi = opts.dpi ?? DEFAULT_DPI;
160160
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
160161
+ const cacheKey = makeCacheKey(source, format, dpi);
160162
+ const hit = cache.get(cacheKey);
160163
+ if (hit) return hit;
160164
+ const jarPath = resolveJar();
160165
+ let timer = null;
160166
+ try {
160167
+ const buffer = await Promise.race([
160168
+ spawnPlantUml(jarPath, source, format, dpi),
160169
+ new Promise((_2, reject) => {
160170
+ timer = setTimeout(
160171
+ () => reject(new Error(`PlantUML render timed out after ${timeoutMs}ms`)),
160172
+ timeoutMs
160173
+ );
160174
+ })
160175
+ ]);
160176
+ if (timer) clearTimeout(timer);
160177
+ const rendered = {
160178
+ buffer,
160179
+ format,
160180
+ contentType: format === "png" ? PNG_CT : SVG_CT,
160181
+ cacheKey
160182
+ };
160183
+ cache.set(cacheKey, rendered);
160184
+ return rendered;
160185
+ } catch (err) {
160186
+ if (timer) clearTimeout(timer);
160187
+ if (isJavaMissing(err)) {
160188
+ throw new Error(
160189
+ `PlantUML render failed: spawn java ENOENT. Install Java 21+ (https://adoptium.net/) and ensure 'java -version' works on PATH. Original: ${err?.message ?? err}`
160190
+ );
160191
+ }
160192
+ throw new Error(`PlantUML render failed: ${err?.message ?? err}`);
160193
+ }
160194
+ }
160195
+
160196
+ // src/tools/confluence.ts
160010
160197
  var STORAGE_BODY_WARN_BYTES = 2 * 1024 * 1024;
160011
160198
  var registerConfluenceTools = (ctx2) => {
160012
160199
  const { server: server2, confluence: confluence2 } = ctx2;
@@ -160065,8 +160252,11 @@ var registerConfluenceTools = (ctx2) => {
160065
160252
  markdown: external_exports.string().optional().describe("Document content in markdown (inline). markdown_file\uACFC \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00. Headings (#), bullet/ordered lists, GFM tables, fenced code blocks, and inline marks (**bold**/*italic*/`code`) are supported."),
160066
160253
  markdown_file: external_exports.string().optional().describe("\uB85C\uCEEC \uB9C8\uD06C\uB2E4\uC6B4 \uD30C\uC77C \uACBD\uB85C. \uD070 \uBB38\uC11C(25KB+)\uB294 \uBAA8\uB378 \uD234 \uD638\uCD9C \uCD9C\uB825 \uD55C\uACC4\uB85C inline markdown \uC778\uC790\uAC00 \uC798\uB9B4 \uC218 \uC788\uC5B4 \uD30C\uC77C \uC9C1\uC811 \uC77D\uAE30 \uAD8C\uC7A5. markdown\uACFC \uB3D9\uC2DC \uC0AC\uC6A9 \uBD88\uAC00."),
160067
160254
  parent_page_id: external_exports.string().optional().describe("Parent page ID \u2014 create mode only, nests the new page under this parent. Ignored in update mode."),
160068
- 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.")
160069
- }, async ({ space_key, title, markdown, markdown_file, parent_page_id, page_id }) => {
160255
+ 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."),
160256
+ render_diagrams: external_exports.boolean().optional().default(true).describe("Render ```plantuml fences to PNG/SVG and embed as attachments. Default true. Falls back to code block on render failure."),
160257
+ diagram_format: external_exports.enum(["png", "svg"]).optional().default("png").describe("Image format for rendered diagrams. Default png."),
160258
+ diagram_dpi: external_exports.number().optional().default(96).describe("Rendering DPI (PNG only, ignored for SVG). Default 96.")
160259
+ }, async ({ space_key, title, markdown, markdown_file, parent_page_id, page_id, render_diagrams = true, diagram_format = "png", diagram_dpi = 96 }) => {
160070
160260
  if (!markdown && !markdown_file) {
160071
160261
  throw new Error("Either markdown or markdown_file must be provided");
160072
160262
  }
@@ -160083,22 +160273,60 @@ var registerConfluenceTools = (ctx2) => {
160083
160273
  } else {
160084
160274
  md = markdown;
160085
160275
  }
160086
- const body = markdownToStorageFormat(md);
160087
- const bodyBytes = Buffer.byteLength(body, "utf8");
160276
+ const { storage, pumlRegistry } = markdownToStorageFormat(md);
160277
+ console.error(`[confluence_publish_doc] pumlRegistry.size=${pumlRegistry.size}, render_diagrams=${render_diagrams}, md_len=${md.length}`);
160278
+ const bodyBytes = Buffer.byteLength(storage, "utf8");
160088
160279
  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;
160089
160280
  if (warning) console.error(`[confluence_publish_doc] ${warning}`);
160281
+ let diagramStats = { rendered: 0, failed: 0, java_missing: false };
160282
+ const renderedDiagrams = /* @__PURE__ */ new Map();
160283
+ if (render_diagrams && pumlRegistry.size > 0) {
160284
+ for (const [id, source] of pumlRegistry) {
160285
+ try {
160286
+ const diag = await renderPlantUml(source, { format: diagram_format, dpi: diagram_dpi });
160287
+ renderedDiagrams.set(id, diag);
160288
+ diagramStats.rendered++;
160289
+ } catch (err) {
160290
+ diagramStats.failed++;
160291
+ if (/Install Java 21\+/.test(err?.message ?? "")) diagramStats.java_missing = true;
160292
+ console.error(`[confluence_publish_doc] plantuml render failed for ${id}: ${err?.message ?? err}`);
160293
+ }
160294
+ }
160295
+ }
160090
160296
  let result;
160091
160297
  let mode;
160298
+ let currentPageId;
160299
+ let currentVersion;
160300
+ let versionsUsed = 1;
160092
160301
  if (page_id) {
160093
160302
  mode = "update";
160094
160303
  const current = await confluence2.getPage(page_id, ["version"]);
160095
- const currentVersion = current?.version?.number ?? 0;
160096
- result = await confluence2.updatePage(page_id, title, body, currentVersion);
160304
+ currentVersion = current?.version?.number ?? 0;
160305
+ result = await confluence2.updatePage(page_id, title, storage, currentVersion);
160306
+ currentPageId = page_id;
160097
160307
  } else {
160098
160308
  mode = "create";
160099
- result = await confluence2.createPage(space_key, title, body, parent_page_id);
160309
+ result = await confluence2.createPage(space_key, title, storage, parent_page_id);
160310
+ currentPageId = result?.id;
160311
+ currentVersion = result?.version?.number ?? 1;
160312
+ }
160313
+ let finalStorage = storage;
160314
+ if (render_diagrams && renderedDiagrams.size > 0) {
160315
+ for (const [id, diag] of renderedDiagrams) {
160316
+ const filename = `diagram-${id}.${diag.format}`;
160317
+ try {
160318
+ await confluence2.addAttachment(currentPageId, filename, diag.buffer, diag.contentType);
160319
+ } catch (err) {
160320
+ console.error(`[confluence_publish_doc] attachment upload failed for ${id}: ${err?.message ?? err}`);
160321
+ renderedDiagrams.delete(id);
160322
+ diagramStats.rendered--;
160323
+ }
160324
+ }
160325
+ finalStorage = replacePumlMarkers(storage, renderedDiagrams, pumlRegistry);
160326
+ result = await confluence2.updatePage(currentPageId, title, finalStorage, currentVersion);
160327
+ versionsUsed = 2;
160100
160328
  }
160101
- const pageIdOut = result?.id ?? page_id;
160329
+ const pageIdOut = result?.id ?? currentPageId;
160102
160330
  const webui = result?._links?.webui;
160103
160331
  const url = webui ? webui.startsWith("http") ? webui : `${confluence2.getBaseUrl()}${webui}` : void 0;
160104
160332
  return {
@@ -160110,17 +160338,41 @@ var registerConfluenceTools = (ctx2) => {
160110
160338
  title,
160111
160339
  space_key,
160112
160340
  version: result?.version?.number,
160341
+ versions_used: versionsUsed,
160113
160342
  url,
160114
160343
  input_chars: md.length,
160115
160344
  input_source: markdown_file ?? "inline",
160116
- body_chars: body.length,
160117
- body_bytes: bodyBytes,
160345
+ body_chars: finalStorage.length,
160346
+ body_bytes: Buffer.byteLength(finalStorage, "utf8"),
160347
+ diagram_stats: diagramStats,
160348
+ puml_blocks_detected: pumlRegistry.size,
160118
160349
  ...warning ? { warning } : {}
160119
160350
  }, null, 2)
160120
160351
  }]
160121
160352
  };
160122
160353
  });
160123
160354
  };
160355
+ function replacePumlMarkers(storage, rendered, pumlRegistry) {
160356
+ let out = storage;
160357
+ for (const [id, diag] of rendered) {
160358
+ const filename = `diagram-${id}.${diag.format}`;
160359
+ const imageMacro = `<ac:image ac:alt="PlantUML Diagram ${id}"><ri:attachment ri:filename="${filename}"/></ac:image>`;
160360
+ out = out.replace(
160361
+ new RegExp(`<p data-puml-id="${id}"></p>`),
160362
+ imageMacro
160363
+ );
160364
+ }
160365
+ for (const [id, source] of pumlRegistry) {
160366
+ if (!rendered.has(id)) {
160367
+ const safeSource = source.replace(/]]>/g, "]]]]><![CDATA[>");
160368
+ out = out.replace(
160369
+ new RegExp(`<p data-puml-id="${id}"></p>`),
160370
+ `<ac:structured-macro ac:name="code"><ac:plain-text-body><![CDATA[${safeSource}]]></ac:plain-text-body></ac:structured-macro>`
160371
+ );
160372
+ }
160373
+ }
160374
+ return out;
160375
+ }
160124
160376
 
160125
160377
  // src/config/risk-models.ts
160126
160378
  var RISK_MODELS = {
@@ -161107,123 +161359,174 @@ var TOOL_CATALOG = [
161107
161359
  category: "\uBB38\uC11C \uB9AC\uBDF0"
161108
161360
  }
161109
161361
  ];
161110
- var WORKFLOWS = {
161111
- pa: [
161112
- "=== PA Gate \uC6CC\uD06C\uD50C\uB85C\uC6B0 ===",
161113
- "",
161114
- "1. iec_create_gate \u2192 PA Gate + 7\uAC1C Document \uD2F0\uCF13 \uC0DD\uC131",
161115
- "2. [Intended Use] Document \u2192 IU \uD2F0\uCF13 \uC0DD\uC131 (\uC0AC\uC6A9\uC790, \uD658\uACBD, \uBAA9\uC801 \uC815\uC758)",
161116
- "3. [System Requirement Spec] \u2192 SyRS \uD2F0\uCF13 \uC0DD\uC131 (IU \uAE30\uBC18 \uC2DC\uC2A4\uD15C \uC694\uAD6C\uC0AC\uD56D)",
161117
- "4. [Classification] \u2192 \uBD84\uB958 \uD2F0\uCF13 (MDR Class \uD310\uC815)",
161118
- "5. [SW Development Plan] \u2192 \uAC1C\uBC1C \uACC4\uD68D\uC11C \uC791\uC131",
161119
- "6. [Risk Management Plan] \u2192 \uB9AC\uC2A4\uD06C \uAD00\uB9AC \uACC4\uD68D\uC11C \uC791\uC131",
161120
- "7. [Security Management Plan] \u2192 \uBCF4\uC548 \uAD00\uB9AC \uACC4\uD68D\uC11C (IEC 81001-5-1)",
161121
- "8. [Configuration Management Plan] \u2192 \uD615\uC0C1 \uAD00\uB9AC \uACC4\uD68D\uC11C",
161122
- "",
161123
- "\uC0AC\uC6A9\uC131 \uACF5\uD559 (IEC 62366-1)\uB3C4 PA \uB2E8\uACC4\uC5D0\uC11C \uC2DC\uC791:",
161124
- "9. usability_create_use_spec \u2192 Use Specification \uC815\uC758",
161125
- "10. usability_create_scenarios \u2192 Normal/Abnormal \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 \uC2DD\uBCC4"
161126
- ].join("\n"),
161127
- ea: [
161128
- "=== EA Gate \uC6CC\uD06C\uD50C\uB85C\uC6B0 ===",
161129
- "",
161130
- "1. iec_create_gate \u2192 EA Gate + 8\uAC1C Document \uD2F0\uCF13 \uC0DD\uC131 (living documents)",
161131
- "2. [Risk Management Report] \u2192 risk_create_hazards (FMEA) + usability_link_to_hazard",
161132
- "3. [SW Requirements Spec] \u2192 FR/NFR \uC815\uB9AC + risk_create_mitigation",
161133
- "4. [SW Architecture Document] \u2192 \uC544\uD0A4\uD14D\uCC98 \uACB0\uC815",
161134
- "5. [SW Detailed Design Document] \u2192 \uC720\uB2DB \uC0C1\uC138\uC124\uACC4 + bottom-up Tasks",
161135
- "6. [SOUP List] \u2192 doc_render(soup_list)",
161136
- "7. [Usability Evaluation Plan] \u2192 usability_create_use_spec/scenarios \uC5F0\uACC4 + doc_render",
161137
- "8. [Clinical Evaluation Plan] \u2192 doc_render(clinical_evaluation_plan)",
161138
- "9. [List of Hazard-Related Use Scenarios] \u2192 usability_create_scenarios(abnormal) \uC5F0\uACC4",
161139
- "",
161140
- "EA 8\uAC1C \uBB38\uC11C = living document (IEC 62304 evolutionary \uBAA8\uB378):",
161141
- " \u2022 EA: \uCD08\uAE30 \uACB0\uC815\xB7\uC2B9\uC778 / ER: \uAC1C\uBC1C\xB7\uAC80\uC99D \uC911 \uAC31\uC2E0 / CA: \uCD5C\uC885 \uD655\uC815",
161142
- " \u2022 \uAC31\uC2E0 \uD2B8\uB9AC\uAC70:",
161143
- " - SRS: \uC694\uAD6C\uC0AC\uD56D \uBCC0\uACBD/\uCD94\uAC00 (change-evaluation-list \uC5F0\uACC4)",
161144
- " - SAD: \uC544\uD0A4\uD14D\uCC98 \uBCC0\uACBD/\uC0C1\uC138\uD654 (architecture-checklist)",
161145
- " - SDD: sprint \uC720\uB2DB \uCD94\uAC00/\uC0C1\uC138\uD654",
161146
- " - FMEA Risk Table: \uC0C8 \uC704\uD5D8\xB7\uD1B5\uC81C \uC2DD\uBCC4 (hazard \uCD94\uAC00)",
161147
- " - SOUP List: \uC758\uC874\uC131 \uBCC0\uACBD (\u2192 CA \uCD5C\uC885 SBOM \uAE30\uBC18 \uD655\uC815)",
161148
- " - Usability/Clinical Eval Plan: \uD3C9\uAC00 \uACB0\uACFC \uBC18\uC601\xB7\uACC4\uD68D \uC870\uC815",
161149
- " - Hazard-Related Use Scenarios: \uC0C8 \uC2DC\uB098\uB9AC\uC624 \uC2DD\uBCC4",
161150
- "",
161151
- "\uAC80\uC99D:",
161152
- "10. xray_create_test \u2192 \uD14C\uC2A4\uD2B8 \uCF00\uC774\uC2A4 \uC0DD\uC131",
161153
- "11. usability_create_validation \u2192 \uC0AC\uC6A9\uC131 \uAC80\uC99D",
161154
- "12. doc_check_completeness \u2192 \uAE30\uC220 \uBB38\uC11C \uC644\uC804\uC131 \uAC80\uC99D",
161155
- "13. doc_generate_evidence \u2192 NB \uC2EC\uC0AC\uC6A9 \uC99D\uAC70 \uD328\uD0A4\uC9C0 \uC0DD\uC131"
161156
- ].join("\n"),
161157
- risk: [
161158
- "=== \uB9AC\uC2A4\uD06C \uAD00\uB9AC \uC6CC\uD06C\uD50C\uB85C\uC6B0 (3 \uBAA8\uB378 \uBA40\uD2F0\uC785\uB825, 0.1.21+) ===",
161159
- "",
161160
- "\uB3C4\uAD6C \uC778\uD130\uD398\uC774\uC2A4:",
161161
- "- 3 \uBAA8\uB378 \uB3D9\uC2DC \uC785\uB825 \uAC00\uB2A5 \u2014 p1_p2_iso14971 (Severity/P1/P2, ISO 14971) / rpn (Severity/Occurrence/Detectability, FMEA RPN) / cia (Confidentiality/Availability/Integrity + Likelihood, Cyber Sec)",
161162
- "- \uC635\uC158\uC740 name \uBB38\uC790\uC5F4 \uC785\uB825 \u2014 \uB3C4\uAD6C\uAC00 UUID \uC790\uB3D9 \uB9E4\uD551 (SoftComply RMP \uACF5\uC2DD schema)",
161163
- "- lookup \uC2E4\uD328 \uC635\uC158\uC740 SKIPPED \uD45C\uC2DC, \uB098\uBA38\uC9C0 \uC815\uC0C1 \uC9C4\uD589",
161164
- "",
161165
- "1. risk_get_config \u2192 \uD504\uB85C\uC81D\uD2B8\uC758 3 \uBAA8\uB378 \uC815\uC758 + \uC635\uC158 name \uBAA9\uB85D \uC870\uD68C",
161166
- "2. IU/SyRS \uBD84\uC11D \u2192 Hazard \uC2DD\uBCC4 (P1-P2 ISO 14971)",
161167
- "3. risk_create_hazards \u2192 Hazard \uC77C\uAD04 \uC0DD\uC131 + 3 \uBAA8\uB378 risk values \uC790\uB3D9 \uC14B\uD305 + Risk Source/Relates \uB9C1\uD06C",
161168
- " - hazard \uAC1D\uCCB4 \uC548\uC5D0 p1_p2_iso14971 / rpn / cia \uC635\uC154\uB110 \uAC1D\uCCB4 \uC785\uB825. \uC785\uB825\uD55C \uBAA8\uB378\uB9CC \uC790\uB3D9 setValues \uD638\uCD9C",
161169
- "4. risk_set_values \u2192 \uAE30\uC874 Hazard\uC5D0 \uCD94\uAC00/\uC218\uC815 risk \uAC12 \uC785\uB825 (3 \uBAA8\uB378 \uB3D9\uC2DC \uB610\uB294 \uBD80\uBD84)",
161170
- "5. \uC644\uD654 \uC870\uCE58 \uC124\uACC4 \u2192 risk_create_mitigation \u2192 Requirement \uC0DD\uC131 + Hazard Current P2 \uC790\uB3D9 \uC5C5\uB370\uC774\uD2B8 (P1-P2 \uBAA8\uB378)",
161171
- "6. RMP Panel\uC5D0\uC11C \uC9C1\uC811 \uC785\uB825\uD558\uB294 \uBAA8\uB378 (Risk Score Calculation / Cyber Sec Risk Model) \uC740",
161172
- " risk_create_hazards / risk_set_values \uC758 rpn / cia \uAC1D\uCCB4\uB85C \uC790\uB3D9 \uC14B\uD305 \uAC00\uB2A5 \u2014 UI \uC218\uB3D9 \uBD88\uD544\uC694",
161173
- "7. traceability_check \u2192 \uCD94\uC801\uC131 \uD655\uC778",
161174
- "8. traceability_find_missing \u2192 \uB204\uB77D \uB9C1\uD06C \uAC80\uC0C9",
161175
- "9. traceability_fix \u2192 \uB204\uB77D \uB9C1\uD06C \uC790\uB3D9 \uC0DD\uC131"
161176
- ].join("\n"),
161177
- usability: [
161178
- "=== \uC0AC\uC6A9\uC131 \uACF5\uD559 (IEC 62366-1) \uC6CC\uD06C\uD50C\uB85C\uC6B0 ===",
161179
- "",
161180
- "1. usability_create_use_spec \u2192 Use Specification \uC815\uC758",
161181
- " (\uC0AC\uC6A9\uC790 \uD504\uB85C\uD30C\uC77C, \uC0AC\uC6A9 \uD658\uACBD, \uC758\uB3C4\uB41C \uC0AC\uC6A9)",
161182
- "2. usability_create_scenarios \u2192 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 \uC2DD\uBCC4",
161183
- " - Normal Use: \uC815\uC0C1 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624",
161184
- " - Abnormal Use: \uBE44\uC815\uC0C1 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 (Hazard \uC6D0\uCC9C)",
161185
- "3. usability_link_to_hazard \u2192 \uC2DC\uB098\uB9AC\uC624 \u2192 Hazard \uC5F0\uACB0",
161186
- " (Risk Source \uB9C1\uD06C\uB85C \uC790\uB3D9 \uC5F0\uACB0)",
161187
- "4. usability_create_validation \u2192 \uC0AC\uC6A9\uC131 \uAC80\uC99D \uACB0\uACFC \uAE30\uB85D",
161188
- " (\uAC80\uC99D \uBC29\uBC95, \uACB0\uACFC, \uC99D\uBE59)"
161189
- ].join("\n"),
161190
- mdr: [
161191
- "=== MDR \uAE30\uC220 \uBB38\uC11C (Annex II) \uC6CC\uD06C\uD50C\uB85C\uC6B0 ===",
161192
- "",
161193
- "1. doc_create_from_template \u2192 Annex II \uAD6C\uC870\uB85C \uBB38\uC11C \uD2F0\uCF13 \uC0DD\uC131",
161194
- "2. \uAC01 \uC139\uC158\uBCC4 \uB0B4\uC6A9 \uC791\uC131",
161195
- "3. doc_check_completeness \u2192 \uB204\uB77D/\uBBF8\uC2B9\uC778 \uD56D\uBAA9 \uAC80\uC0AC",
161196
- "4. doc_generate_evidence \u2192 \uC99D\uAC70 \uD328\uD0A4\uC9C0 \uC0DD\uC131",
161197
- " - \uCD94\uC801\uC131 \uB9E4\uD2B8\uB9AD\uC2A4 (IU \u2192 SyRS \u2192 Req \u2192 DD \u2192 Task \u2192 Test)",
161198
- " - \uB9AC\uC2A4\uD06C \uB9E4\uD2B8\uB9AD\uC2A4 (Hazard \xD7 Severity \xD7 P1/P2)",
161199
- " - V&V \uC694\uC57D (\uD14C\uC2A4\uD2B8 \uD1B5\uACFC\uC728)"
161200
- ].join("\n"),
161201
- customfield: [
161202
- "=== customfield / \uBB38\uC11C \uD544\uB4DC \uCC44\uC6B0\uAE30 \uC6CC\uD06C\uD50C\uB85C\uC6B0 ===",
161203
- "",
161204
- "\uC8FC\uC758: customfield \uC911 \uC77C\uBD80\uB294 rich text(ADF)\uB97C \uC694\uAD6C\uD558\uC9C0\uB9CC, createmeta\uAC00 string\uC73C\uB85C",
161205
- '\uC798\uBABB \uBCF4\uACE0\uD558\uB294 \uACBD\uC6B0\uAC00 \uC788\uB2E4 (\uBE48 \uD2F0\uCF13 "\uBE48 \uAE61\uD1B5"\uC758 \uC8FC\uC6D0\uC778). \uC544\uB798 \uC808\uCC28\uB85C \uBC29\uC9C0\uD55C\uB2E4.',
161206
- "",
161207
- "1. jira_get_issue_type_schema \u2192 \uC774\uC288\uD0C0\uC785 \uD544\uB4DC \uAD6C\uC870 + inferredFormat \uD655\uC778",
161208
- " inferredFormat \uAC12: adf(\uD655\uC815) / adf-suspect(string\uC774\uB098 textarea, \uC758\uC2EC) /",
161209
- " option / string / number / date ...",
161210
- "2. ADF \uD544\uB4DC(customfield_*) \uC2DD\uBCC4 \u2014 inferredFormat\uC774 adf \uB610\uB294 adf-suspect\uC778 \uD544\uB4DC",
161211
- "3. jira_create_issue / jira_update_issue \uD638\uCD9C \uC2DC:",
161212
- " - \uC790\uB3D9 \uAC10\uC9C0: auto_detect_rich_text: true (schema\uC5D0\uC11C ADF \uD544\uB4DC \uC790\uB3D9 \uCD94\uC815)",
161213
- ' - \uBA85\uC2DC \uC9C0\uC815: rich_text_custom_fields: ["customfield_xxxx", ...] (\uCD5C\uC885 \uBCF4\uC218)',
161214
- " - \uD3C9\uBB38\uC744 \uB118\uAE30\uBA74 \uC790\uB3D9\uC73C\uB85C ADF\uB85C \uBCC0\uD658\uB428 (\uC774\uBBF8 ADF \uAC1D\uCCB4\uBA74 \uADF8\uB300\uB85C)",
161215
- '4. option \uD544\uB4DC(\uC608: Phase)\uB294 { value: "PA" } \uD615\uD0DC\uB85C extra_fields/fields\uC5D0 \uC804\uB2EC',
161216
- "",
161217
- "\uD45C\uC900 \uC6D0\uCE59: \uBB38\uC11C \uD2F0\uCF13\uC744 \uB9CC\uB4E4 \uB54C\uB294 \uBC18\uB4DC\uC2DC schema\uB97C \uBA3C\uC800 \uBCF4\uACE0,",
161218
- "customfield\uB97C \uADF8 \uAD6C\uC870\uC5D0 \uB9DE\uCDB0 \uCC44\uC6B4\uB2E4 \u2014 \uBE48 \uD2F0\uCF13\uC744 \uBC29\uCE58\uD558\uC9C0 \uC54A\uB294\uB2E4."
161219
- ].join("\n")
161220
- };
161362
+ var WORKFLOWS = [
161363
+ {
161364
+ id: "pa",
161365
+ title: "PA Gate \uC6CC\uD06C\uD50C\uB85C\uC6B0",
161366
+ description: "IEC 62304 PA(Planning Analysis) \uB2E8\uACC4 \u2014 \uC0AC\uC6A9\uC790/\uD658\uACBD/\uC758\uB3C4\uB41C \uC0AC\uC6A9 \uC815\uC758 \u2192 \uC2DC\uC2A4\uD15C \uC694\uAD6C\uC0AC\uD56D \u2192 \uBD84\uB958 \u2192 \uAC1C\uBC1C/\uB9AC\uC2A4\uD06C/\uBCF4\uC548/\uD615\uC0C1 \uAD00\uB9AC \uACC4\uD68D.",
161367
+ steps: [
161368
+ "1. iec_create_gate \u2192 PA Gate + 7\uAC1C Document \uD2F0\uCF13 \uC0DD\uC131",
161369
+ "2. [Intended Use] Document \u2192 IU \uD2F0\uCF13 \uC0DD\uC131 (\uC0AC\uC6A9\uC790, \uD658\uACBD, \uBAA9\uC801 \uC815\uC758)",
161370
+ "3. [System Requirement Spec] \u2192 SyRS \uD2F0\uCF13 \uC0DD\uC131 (IU \uAE30\uBC18 \uC2DC\uC2A4\uD15C \uC694\uAD6C\uC0AC\uD56D)",
161371
+ "4. [Classification] \u2192 \uBD84\uB958 \uD2F0\uCF13 (MDR Class \uD310\uC815)",
161372
+ "5. [SW Development Plan] \u2192 \uAC1C\uBC1C \uACC4\uD68D\uC11C \uC791\uC131",
161373
+ "6. [Risk Management Plan] \u2192 \uB9AC\uC2A4\uD06C \uAD00\uB9AC \uACC4\uD68D\uC11C \uC791\uC131",
161374
+ "7. [Security Management Plan] \u2192 \uBCF4\uC548 \uAD00\uB9AC \uACC4\uD68D\uC11C (IEC 81001-5-1)",
161375
+ "8. [Configuration Management Plan] \u2192 \uD615\uC0C1 \uAD00\uB9AC \uACC4\uD68D\uC11C",
161376
+ "",
161377
+ "\uC0AC\uC6A9\uC131 \uACF5\uD559 (IEC 62366-1)\uB3C4 PA \uB2E8\uACC4\uC5D0\uC11C \uC2DC\uC791:",
161378
+ "9. usability_create_use_spec \u2192 Use Specification \uC815\uC758",
161379
+ "10. usability_create_scenarios \u2192 Normal/Abnormal \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 \uC2DD\uBCC4"
161380
+ ],
161381
+ related: ["iec_create_gate", "usability_create_use_spec", "usability_create_scenarios"]
161382
+ },
161383
+ {
161384
+ id: "ea",
161385
+ title: "EA Gate \uC6CC\uD06C\uD50C\uB85C\uC6B0",
161386
+ description: "IEC 62304 EA(Engineering Analysis) \uB2E8\uACC4 \u2014 8\uAC1C living document(RMR/SRS/SAD/SDD/SOUP List/Usability Eval Plan/Clinical Eval Plan/Hazard-Related Use Scenarios). \uCD08\uAE30 \uACB0\uC815 \u2192 ER \uAC31\uC2E0 \u2192 CA \uD655\uC815.",
161387
+ steps: [
161388
+ "1. iec_create_gate \u2192 EA Gate + 8\uAC1C Document \uD2F0\uCF13 \uC0DD\uC131 (living documents)",
161389
+ "2. [Risk Management Report] \u2192 risk_create_hazards (FMEA) + usability_link_to_hazard",
161390
+ "3. [SW Requirements Spec] \u2192 FR/NFR \uC815\uB9AC + risk_create_mitigation",
161391
+ "4. [SW Architecture Document] \u2192 \uC544\uD0A4\uD14D\uCC98 \uACB0\uC815",
161392
+ "5. [SW Detailed Design Document] \u2192 \uC720\uB2DB \uC0C1\uC138\uC124\uACC4 + bottom-up Tasks",
161393
+ "6. [SOUP List] \u2192 doc_render(soup_list)",
161394
+ "7. [Usability Evaluation Plan] \u2192 usability_create_use_spec/scenarios \uC5F0\uACC4 + doc_render",
161395
+ "8. [Clinical Evaluation Plan] \u2192 doc_render(clinical_evaluation_plan)",
161396
+ "9. [List of Hazard-Related Use Scenarios] \u2192 usability_create_scenarios(abnormal) \uC5F0\uACC4",
161397
+ "",
161398
+ "EA 8\uAC1C \uBB38\uC11C = living document (IEC 62304 evolutionary \uBAA8\uB378):",
161399
+ " \u2022 EA: \uCD08\uAE30 \uACB0\uC815\xB7\uC2B9\uC778 / ER: \uAC1C\uBC1C\xB7\uAC80\uC99D \uC911 \uAC31\uC2E0 / CA: \uCD5C\uC885 \uD655\uC815",
161400
+ " \u2022 \uAC31\uC2E0 \uD2B8\uB9AC\uAC70:",
161401
+ " - SRS: \uC694\uAD6C\uC0AC\uD56D \uBCC0\uACBD/\uCD94\uAC00 (change-evaluation-list \uC5F0\uACC4)",
161402
+ " - SAD: \uC544\uD0A4\uD14D\uCC98 \uBCC0\uACBD/\uC0C1\uC138\uD654 (architecture-checklist)",
161403
+ " - SDD: sprint \uC720\uB2DB \uCD94\uAC00/\uC0C1\uC138\uD654",
161404
+ " - FMEA Risk Table: \uC0C8 \uC704\uD5D8\xB7\uD1B5\uC81C \uC2DD\uBCC4 (hazard \uCD94\uAC00)",
161405
+ " - SOUP List: \uC758\uC874\uC131 \uBCC0\uACBD (\u2192 CA \uCD5C\uC885 SBOM \uAE30\uBC18 \uD655\uC815)",
161406
+ " - Usability/Clinical Eval Plan: \uD3C9\uAC00 \uACB0\uACFC \uBC18\uC601\xB7\uACC4\uD68D \uC870\uC815",
161407
+ " - Hazard-Related Use Scenarios: \uC0C8 \uC2DC\uB098\uB9AC\uC624 \uC2DD\uBCC4",
161408
+ "",
161409
+ "\uAC80\uC99D:",
161410
+ "10. xray_create_test \u2192 \uD14C\uC2A4\uD2B8 \uCF00\uC774\uC2A4 \uC0DD\uC131",
161411
+ "11. usability_create_validation \u2192 \uC0AC\uC6A9\uC131 \uAC80\uC99D",
161412
+ "12. doc_check_completeness \u2192 \uAE30\uC220 \uBB38\uC11C \uC644\uC804\uC131 \uAC80\uC99D",
161413
+ "13. doc_generate_evidence \u2192 NB \uC2EC\uC0AC\uC6A9 \uC99D\uAC70 \uD328\uD0A4\uC9C0 \uC0DD\uC131"
161414
+ ],
161415
+ related: ["iec_create_gate", "risk_create_hazards", "risk_create_mitigation", "doc_render", "xray_create_test", "usability_create_validation", "doc_check_completeness", "doc_generate_evidence"]
161416
+ },
161417
+ {
161418
+ id: "risk",
161419
+ title: "\uB9AC\uC2A4\uD06C \uAD00\uB9AC \uC6CC\uD06C\uD50C\uB85C\uC6B0 (3 \uBAA8\uB378 \uBA40\uD2F0\uC785\uB825, 0.1.21+)",
161420
+ description: "SoftComply Risk Management Plugin \u2014 P1-P2 ISO 14971 / RPN FMEA / CIA Cyber Sec 3 \uBAA8\uB378 \uB3D9\uC2DC \uC785\uB825. \uC635\uC158 name \u2192 UUID \uC790\uB3D9 \uB9E4\uD551.",
161421
+ steps: [
161422
+ "\uB3C4\uAD6C \uC778\uD130\uD398\uC774\uC2A4:",
161423
+ "- 3 \uBAA8\uB378 \uB3D9\uC2DC \uC785\uB825 \uAC00\uB2A5 \u2014 p1_p2_iso14971 (Severity/P1/P2, ISO 14971) / rpn (Severity/Occurrence/Detectability, FMEA RPN) / cia (Confidentiality/Availability/Integrity + Likelihood, Cyber Sec)",
161424
+ "- \uC635\uC158\uC740 name \uBB38\uC790\uC5F4 \uC785\uB825 \u2014 \uB3C4\uAD6C\uAC00 UUID \uC790\uB3D9 \uB9E4\uD551 (SoftComply RMP \uACF5\uC2DD schema)",
161425
+ "- lookup \uC2E4\uD328 \uC635\uC158\uC740 SKIPPED \uD45C\uC2DC, \uB098\uBA38\uC9C0 \uC815\uC0C1 \uC9C4\uD589",
161426
+ "",
161427
+ "1. risk_get_config \u2192 \uD504\uB85C\uC81D\uD2B8\uC758 3 \uBAA8\uB378 \uC815\uC758 + \uC635\uC158 name \uBAA9\uB85D \uC870\uD68C",
161428
+ "2. IU/SyRS \uBD84\uC11D \u2192 Hazard \uC2DD\uBCC4 (P1-P2 ISO 14971)",
161429
+ "3. risk_create_hazards \u2192 Hazard \uC77C\uAD04 \uC0DD\uC131 + 3 \uBAA8\uB378 risk values \uC790\uB3D9 \uC14B\uD305 + Risk Source/Relates \uB9C1\uD06C",
161430
+ " - hazard \uAC1D\uCCB4 \uC548\uC5D0 p1_p2_iso14971 / rpn / cia \uC635\uC154\uB110 \uAC1D\uCCB4 \uC785\uB825. \uC785\uB825\uD55C \uBAA8\uB378\uB9CC \uC790\uB3D9 setValues \uD638\uCD9C",
161431
+ "4. risk_set_values \u2192 \uAE30\uC874 Hazard\uC5D0 \uCD94\uAC00/\uC218\uC815 risk \uAC12 \uC785\uB825 (3 \uBAA8\uB378 \uB3D9\uC2DC \uB610\uB294 \uBD80\uBD84)",
161432
+ "5. \uC644\uD654 \uC870\uCE58 \uC124\uACC4 \u2192 risk_create_mitigation \u2192 Requirement \uC0DD\uC131 + Hazard Current P2 \uC790\uB3D9 \uC5C5\uB370\uC774\uD2B8 (P1-P2 \uBAA8\uB378)",
161433
+ "6. RMP Panel\uC5D0\uC11C \uC9C1\uC811 \uC785\uB825\uD558\uB294 \uBAA8\uB378 (Risk Score Calculation / Cyber Sec Risk Model) \uC740",
161434
+ " risk_create_hazards / risk_set_values \uC758 rpn / cia \uAC1D\uCCB4\uB85C \uC790\uB3D9 \uC14B\uD305 \uAC00\uB2A5 \u2014 UI \uC218\uB3D9 \uBD88\uD544\uC694",
161435
+ "7. traceability_check \u2192 \uCD94\uC801\uC131 \uD655\uC778",
161436
+ "8. traceability_find_missing \u2192 \uB204\uB77D \uB9C1\uD06C \uAC80\uC0C9",
161437
+ "9. traceability_fix \u2192 \uB204\uB77D \uB9C1\uD06C \uC790\uB3D9 \uC0DD\uC131"
161438
+ ],
161439
+ related: ["risk_get_config", "risk_create_hazards", "risk_set_values", "risk_create_mitigation", "traceability_check", "traceability_find_missing", "traceability_fix"]
161440
+ },
161441
+ {
161442
+ id: "usability",
161443
+ title: "\uC0AC\uC6A9\uC131 \uACF5\uD559 (IEC 62366-1) \uC6CC\uD06C\uD50C\uB85C\uC6B0",
161444
+ description: "Use Specification \u2192 Normal/Abnormal \uC2DC\uB098\uB9AC\uC624 \u2192 Hazard \uC5F0\uACB0(Risk Source) \u2192 \uAC80\uC99D \uACB0\uACFC \uAE30\uB85D.",
161445
+ steps: [
161446
+ "1. usability_create_use_spec \u2192 Use Specification \uC815\uC758",
161447
+ " (\uC0AC\uC6A9\uC790 \uD504\uB85C\uD30C\uC77C, \uC0AC\uC6A9 \uD658\uACBD, \uC758\uB3C4\uB41C \uC0AC\uC6A9)",
161448
+ "2. usability_create_scenarios \u2192 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 \uC2DD\uBCC4",
161449
+ " - Normal Use: \uC815\uC0C1 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624",
161450
+ " - Abnormal Use: \uBE44\uC815\uC0C1 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 (Hazard \uC6D0\uCC9C)",
161451
+ "3. usability_link_to_hazard \u2192 \uC2DC\uB098\uB9AC\uC624 \u2192 Hazard \uC5F0\uACB0",
161452
+ " (Risk Source \uB9C1\uD06C\uB85C \uC790\uB3D9 \uC5F0\uACB0)",
161453
+ "4. usability_create_validation \u2192 \uC0AC\uC6A9\uC131 \uAC80\uC99D \uACB0\uACFC \uAE30\uB85D",
161454
+ " (\uAC80\uC99D \uBC29\uBC95, \uACB0\uACFC, \uC99D\uBE59)"
161455
+ ],
161456
+ related: ["usability_create_use_spec", "usability_create_scenarios", "usability_link_to_hazard", "usability_create_validation"]
161457
+ },
161458
+ {
161459
+ id: "mdr",
161460
+ title: "MDR \uAE30\uC220 \uBB38\uC11C (Annex II) \uC6CC\uD06C\uD50C\uB85C\uC6B0",
161461
+ description: "NB(Notified Body) \uC2EC\uC0AC\uC6A9 MDR Annex II \uAE30\uC220\uBB38\uC11C \u2014 \uC644\uC804\uC131 \uAC80\uC0AC + \uCD94\uC801\uC131 \uB9E4\uD2B8\uB9AD\uC2A4 + \uB9AC\uC2A4\uD06C \uB9E4\uD2B8\uB9AD\uC2A4 + V&V \uC694\uC57D.",
161462
+ steps: [
161463
+ "1. doc_create_from_template \u2192 Annex II \uAD6C\uC870\uB85C \uBB38\uC11C \uD2F0\uCF13 \uC0DD\uC131",
161464
+ "2. \uAC01 \uC139\uC158\uBCC4 \uB0B4\uC6A9 \uC791\uC131",
161465
+ "3. doc_check_completeness \u2192 \uB204\uB77D/\uBBF8\uC2B9\uC778 \uD56D\uBAA9 \uAC80\uC0AC",
161466
+ "4. doc_generate_evidence \u2192 \uC99D\uAC70 \uD328\uD0A4\uC9C0 \uC0DD\uC131",
161467
+ " - \uCD94\uC801\uC131 \uB9E4\uD2B8\uB9AD\uC2A4 (IU \u2192 SyRS \u2192 Req \u2192 DD \u2192 Task \u2192 Test)",
161468
+ " - \uB9AC\uC2A4\uD06C \uB9E4\uD2B8\uB9AD\uC2A4 (Hazard \xD7 Severity \xD7 P1/P2)",
161469
+ " - V&V \uC694\uC57D (\uD14C\uC2A4\uD2B8 \uD1B5\uACFC\uC728)"
161470
+ ],
161471
+ related: ["doc_create_from_template", "doc_check_completeness", "doc_generate_evidence"]
161472
+ },
161473
+ {
161474
+ id: "customfield",
161475
+ title: "customfield / \uBB38\uC11C \uD544\uB4DC \uCC44\uC6B0\uAE30 \uC6CC\uD06C\uD50C\uB85C\uC6B0",
161476
+ description: '\uBE48 \uD2F0\uCF13 "\uBE48 \uAE61\uD1B5" \uBC29\uC9C0 \u2014 schema \uC6B0\uC120 \uC870\uD68C \u2192 ADF \uD544\uB4DC \uC2DD\uBCC4 \u2192 \uBA85\uC2DC \uB610\uB294 auto_detect \uBCC0\uD658.',
161477
+ steps: [
161478
+ "\uC8FC\uC758: customfield \uC911 \uC77C\uBD80\uB294 rich text(ADF)\uB97C \uC694\uAD6C\uD558\uC9C0\uB9CC, createmeta\uAC00 string\uC73C\uB85C",
161479
+ '\uC798\uBABB \uBCF4\uACE0\uD558\uB294 \uACBD\uC6B0\uAC00 \uC788\uB2E4 (\uBE48 \uD2F0\uCF13 "\uBE48 \uAE61\uD1B5"\uC758 \uC8FC\uC6D0\uC778). \uC544\uB798 \uC808\uCC28\uB85C \uBC29\uC9C0\uD55C\uB2E4.',
161480
+ "",
161481
+ "1. jira_get_issue_type_schema \u2192 \uC774\uC288\uD0C0\uC785 \uD544\uB4DC \uAD6C\uC870 + inferredFormat \uD655\uC778",
161482
+ " inferredFormat \uAC12: adf(\uD655\uC815) / adf-suspect(string\uC774\uB098 textarea, \uC758\uC2EC) /",
161483
+ " option / string / number / date ...",
161484
+ "2. ADF \uD544\uB4DC(customfield_*) \uC2DD\uBCC4 \u2014 inferredFormat\uC774 adf \uB610\uB294 adf-suspect\uC778 \uD544\uB4DC",
161485
+ "3. jira_create_issue / jira_update_issue \uD638\uCD9C \uC2DC:",
161486
+ " - \uC790\uB3D9 \uAC10\uC9C0: auto_detect_rich_text: true (schema\uC5D0\uC11C ADF \uD544\uB4DC \uC790\uB3D9 \uCD94\uC815)",
161487
+ ' - \uBA85\uC2DC \uC9C0\uC815: rich_text_custom_fields: ["customfield_xxxx", ...] (\uCD5C\uC885 \uBCF4\uC218)',
161488
+ " - \uD3C9\uBB38\uC744 \uB118\uAE30\uBA74 \uC790\uB3D9\uC73C\uB85C ADF\uB85C \uBCC0\uD658\uB428 (\uC774\uBBF8 ADF \uAC1D\uCCB4\uBA74 \uADF8\uB300\uB85C)",
161489
+ '4. option \uD544\uB4DC(\uC608: Phase)\uB294 { value: "PA" } \uD615\uD0DC\uB85C extra_fields/fields\uC5D0 \uC804\uB2EC',
161490
+ "",
161491
+ "\uD45C\uC900 \uC6D0\uCE59: \uBB38\uC11C \uD2F0\uCF13\uC744 \uB9CC\uB4E4 \uB54C\uB294 \uBC18\uB4DC\uC2DC schema\uB97C \uBA3C\uC800 \uBCF4\uACE0,",
161492
+ "customfield\uB97C \uADF8 \uAD6C\uC870\uC5D0 \uB9DE\uCDB0 \uCC44\uC6B4\uB2E4 \u2014 \uBE48 \uD2F0\uCF13\uC744 \uBC29\uCE58\uD558\uC9C0 \uC54A\uB294\uB2E4."
161493
+ ],
161494
+ related: ["jira_get_issue_type_schema", "jira_create_issue", "jira_update_issue"]
161495
+ },
161496
+ {
161497
+ id: "diagram",
161498
+ title: "PlantUML \uB2E4\uC774\uC5B4\uADF8\uB7A8 \uB80C\uB354\uB9C1",
161499
+ description: "confluence_publish_doc / markdown_to_pdf / jira_attach_pdf_from_markdown \uD638\uCD9C \uC2DC ```plantuml \uD39C\uC2A4\uB97C PNG\uB85C \uC790\uB3D9 \uB80C\uB354\uB9C1. Java 21+ \uC0AC\uC804 \uC694\uAD6C. GPL-3.0 \uAC80\uD1A0 \uC644\uB8CC(2026-07-31).",
161500
+ steps: [
161501
+ "1. Java 21+ \uC124\uCE58 \uD655\uC778: java -version (\uC5C6\uC73C\uBA74 https://adoptium.net/)",
161502
+ "2. Graphviz \uBBF8\uC124\uCE58 \uC2DC\uC5D0\uB3C4 \uB3D9\uC791 \u2014 PlantUML\uC774 smetana \uC5D4\uC9C4\uC73C\uB85C layout \uCC98\uB9AC (PlantUML \uAE30\uBCF8 \uD65C\uC131\uD654). \uB2E4\uB9CC \uC77C\uBD80 \uB2E4\uC774\uC5B4\uADF8\uB7A8(\uBCF5\uC7A1 graph)\uC740 Graphviz \uD544\uC694\uD560 \uC218 \uC788\uC74C.",
161503
+ "3. confluence_publish_doc \uD638\uCD9C \uC2DC render_diagrams: true(\uAE30\uBCF8\uAC12)\uB85C \uC790\uB3D9 \uC784\uBCA0\uB4DC. \uC2E4\uD328 \uBE14\uB85D\uC740 \uCF54\uB4DC \uB9E4\uD06C\uB85C\uB85C \uD3F4\uBC31.",
161504
+ "4. markdown_to_pdf / jira_attach_pdf_from_markdown \uD638\uCD9C \uC2DC render_plantuml: true(\uAE30\uBCF8\uAC12)\uB85C PDF \uB0B4 \uC774\uBBF8\uC9C0 \uBC15\uC2A4 \uC0BD\uC785.",
161505
+ "5. \uC635\uD2B8\uC544\uC6C3: render_diagrams: false \uB610\uB294 render_plantuml: false. \uBD80\uBD84 disable \uAC00\uB2A5.",
161506
+ "6. \uACB0\uACFC JSON\uC758 diagram_stats (rendered/failed/java_missing) \uD544\uB4DC\uB85C \uB80C\uB354 \uACB0\uACFC \uD655\uC778."
161507
+ ],
161508
+ related: ["confluence_publish_doc", "markdown_to_pdf", "jira_attach_pdf_from_markdown"]
161509
+ }
161510
+ ];
161511
+ function workflowToText(w2) {
161512
+ const lines = [];
161513
+ lines.push(`=== ${w2.title} ===`);
161514
+ lines.push("");
161515
+ if (w2.description) {
161516
+ lines.push(w2.description);
161517
+ lines.push("");
161518
+ }
161519
+ for (const step of w2.steps) {
161520
+ lines.push(step);
161521
+ }
161522
+ return lines.join("\n");
161523
+ }
161221
161524
 
161222
161525
  // src/tools/help.ts
161223
161526
  var registerHelpTools = ({ server: server2 }) => {
161224
161527
  server2.tool("help", "Show available tools, workflows, and usage guides. Call without arguments for full catalog, or specify a topic.", {
161225
161528
  topic: external_exports.string().optional().describe(
161226
- 'Topic: "tools" (all tools), "workflows" (all workflows), "pa", "ea", "risk", "usability", "mdr", or a specific tool name (e.g. "risk_create_hazards")'
161529
+ 'Topic: "tools" (all tools), "workflows" (all workflows), "pa", "ea", "risk", "usability", "mdr", "customfield", "diagram", or a specific tool name (e.g. "risk_create_hazards")'
161227
161530
  )
161228
161531
  }, async ({ topic }) => {
161229
161532
  if (!topic) {
@@ -161236,19 +161539,21 @@ var registerHelpTools = ({ server: server2 }) => {
161236
161539
  }
161237
161540
  lines.push("");
161238
161541
  }
161239
- lines.push('\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uAC00\uC774\uB4DC: help(topic="workflows") \uB610\uB294 help(topic="pa"), help(topic="ea"), help(topic="risk"), help(topic="usability"), help(topic="mdr")');
161542
+ const wfIds2 = WORKFLOWS.map((w2) => w2.id).join(", ");
161543
+ lines.push(`\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uAC00\uC774\uB4DC: help(topic="workflows") \uB610\uB294 help(topic="<id>") \u2014 \uC0AC\uC6A9 \uAC00\uB2A5 id: ${wfIds2}`);
161240
161544
  lines.push('\uD2B9\uC815 \uD234 \uC0C1\uC138: help(topic="\uD234\uC774\uB984")');
161241
161545
  return { content: [{ type: "text", text: lines.join("\n") }] };
161242
161546
  }
161243
- if (WORKFLOWS[topic]) {
161244
- return { content: [{ type: "text", text: WORKFLOWS[topic] }] };
161547
+ const workflow = WORKFLOWS.find((w2) => w2.id === topic);
161548
+ if (workflow) {
161549
+ return { content: [{ type: "text", text: workflowToText(workflow) }] };
161245
161550
  }
161246
161551
  if (topic === "tools") {
161247
161552
  const lines = TOOL_CATALOG.map((t2) => `${t2.name} [${t2.category}]: ${t2.description}`);
161248
161553
  return { content: [{ type: "text", text: lines.join("\n") }] };
161249
161554
  }
161250
161555
  if (topic === "workflows") {
161251
- const lines = Object.values(WORKFLOWS).join("\n\n");
161556
+ const lines = WORKFLOWS.map(workflowToText).join("\n\n");
161252
161557
  return { content: [{ type: "text", text: lines }] };
161253
161558
  }
161254
161559
  const tool = TOOL_CATALOG.find((t2) => t2.name === topic);
@@ -161258,8 +161563,9 @@ ${tool.description}
161258
161563
 
161259
161564
  \uC790\uC138\uD55C \uD30C\uB77C\uBBF8\uD130\uB294 \uC774 \uD234\uC744 \uC9C1\uC811 \uD638\uCD9C\uD558\uC5EC \uD655\uC778\uD558\uC138\uC694.` }] };
161260
161565
  }
161566
+ const wfIds = WORKFLOWS.map((w2) => w2.id).join(", ");
161261
161567
  return { content: [{ type: "text", text: `\uC54C \uC218 \uC5C6\uB294 \uD1A0\uD53D: "${topic}"
161262
- \uC0AC\uC6A9 \uAC00\uB2A5: tools, workflows, pa, ea, risk, usability, mdr, \uB610\uB294 \uD234 \uC774\uB984` }] };
161568
+ \uC0AC\uC6A9 \uAC00\uB2A5: tools, workflows, ${wfIds}, \uB610\uB294 \uD234 \uC774\uB984` }] };
161263
161569
  });
161264
161570
  };
161265
161571
 
@@ -161747,8 +162053,8 @@ var registerDocValidationTools = (ctx2) => {
161747
162053
  };
161748
162054
 
161749
162055
  // src/tools/init.ts
161750
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync } from "fs";
161751
- import { join as join2 } from "path";
162056
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
162057
+ import { join as join3 } from "path";
161752
162058
  var registerInitTools = ({ server: server2 }) => {
161753
162059
  server2.tool(
161754
162060
  "jira_init_project",
@@ -161758,11 +162064,11 @@ var registerInitTools = ({ server: server2 }) => {
161758
162064
  },
161759
162065
  async ({ project_key }) => {
161760
162066
  const cwd = process.cwd();
161761
- const claudeMdPath = join2(cwd, "CLAUDE.md");
162067
+ const claudeMdPath = join3(cwd, "CLAUDE.md");
161762
162068
  const line = `\uC774 \uD504\uB85C\uC81D\uD2B8\uC758 Jira key\uB294 ${project_key}\uC785\uB2C8\uB2E4.`;
161763
162069
  try {
161764
162070
  let content = "";
161765
- if (existsSync(claudeMdPath)) {
162071
+ if (existsSync2(claudeMdPath)) {
161766
162072
  content = readFileSync2(claudeMdPath, "utf-8");
161767
162073
  if (content.includes(line)) {
161768
162074
  return { content: [{ type: "text", text: `\uC774\uBBF8 CLAUDE.md\uC5D0 ${project_key} \uD504\uB85C\uC81D\uD2B8 \uC124\uC815\uC774 \uC788\uC2B5\uB2C8\uB2E4.` }] };
@@ -161789,7 +162095,7 @@ var registerInitTools = ({ server: server2 }) => {
161789
162095
 
161790
162096
  // src/tools/doc-render.ts
161791
162097
  import { readFileSync as readFileSync3 } from "fs";
161792
- import { fileURLToPath } from "url";
162098
+ import { fileURLToPath as fileURLToPath2 } from "url";
161793
162099
  import path from "path";
161794
162100
 
161795
162101
  // src/utils/doc-render.ts
@@ -161854,7 +162160,7 @@ var TEMPLATE_VERSION = "17d0c4e";
161854
162160
 
161855
162161
  // src/tools/doc-render.ts
161856
162162
  init_adf();
161857
- var __dirname2 = path.dirname(fileURLToPath(import.meta.url));
162163
+ var __dirname2 = path.dirname(fileURLToPath2(import.meta.url));
161858
162164
  var TEMPLATE_DIR = path.resolve(__dirname2, "../vendor/dev-docs-templates/templates");
161859
162165
  var registerDocRenderTools = (ctx2) => {
161860
162166
  const { server: server2, jira: jira2 } = ctx2;
@@ -161949,7 +162255,7 @@ import { execFileSync } from "child_process";
161949
162255
  // src/utils/markdownToPdf.ts
161950
162256
  import { writeFileSync as writeFileSync3 } from "fs";
161951
162257
  import { tmpdir } from "os";
161952
- import { join as join4 } from "path";
162258
+ import { join as join5 } from "path";
161953
162259
  import { randomUUID } from "crypto";
161954
162260
 
161955
162261
  // node_modules/pdfkit/js/pdfkit.es.js
@@ -163841,12 +164147,12 @@ function $e71565f2ce09cb6b$export$69a3209f1a06c04d(target, key, descriptor) {
163841
164147
  let fn = descriptor.value;
163842
164148
  return {
163843
164149
  get() {
163844
- let cache = /* @__PURE__ */ new Map();
164150
+ let cache2 = /* @__PURE__ */ new Map();
163845
164151
  function memoized(...args) {
163846
164152
  let key2 = args.length > 0 ? args[0] : "value";
163847
- if (cache.has(key2)) return cache.get(key2);
164153
+ if (cache2.has(key2)) return cache2.get(key2);
163848
164154
  let result = fn.apply(this, args);
163849
- cache.set(key2, result);
164155
+ cache2.set(key2, result);
163850
164156
  return result;
163851
164157
  }
163852
164158
  Object.defineProperty(this, key, {
@@ -170517,8 +170823,8 @@ var $55f71433a605c87d$export$2e2bcd8739ae039 = class {
170517
170823
  }
170518
170824
  if (count === 1) {
170519
170825
  let result = input.map((g3) => g3.id);
170520
- let cache = this.inputCache[found];
170521
- if (cache) cache.push(result);
170826
+ let cache2 = this.inputCache[found];
170827
+ if (cache2) cache2.push(result);
170522
170828
  else this.inputCache[found] = [
170523
170829
  result
170524
170830
  ];
@@ -182989,14 +183295,14 @@ var SubsetMixin = {
182989
183295
  var ROW_FIELDS = ["height", "minHeight", "maxHeight"];
182990
183296
  var COLUMN_FIELDS = ["width", "minWidth", "maxWidth"];
182991
183297
  function memoize(fn, maxSize) {
182992
- const cache = /* @__PURE__ */ new Map();
183298
+ const cache2 = /* @__PURE__ */ new Map();
182993
183299
  return function(...args) {
182994
183300
  const key = args[0];
182995
- if (!cache.has(key)) {
182996
- cache.set(key, fn(...args));
182997
- if (cache.size > maxSize) cache.delete(cache.keys().next());
183301
+ if (!cache2.has(key)) {
183302
+ cache2.set(key, fn(...args));
183303
+ if (cache2.size > maxSize) cache2.delete(cache2.keys().next());
182998
183304
  }
182999
- return cache.get(key);
183305
+ return cache2.get(key);
183000
183306
  };
183001
183307
  }
183002
183308
  function isObject2(item) {
@@ -184000,23 +184306,23 @@ PDFDocument.LineWrapper = LineWrapper;
184000
184306
  init_marked_esm();
184001
184307
 
184002
184308
  // src/utils/pdf-fonts.ts
184003
- import { existsSync as existsSync2 } from "fs";
184004
- import { join as join3, dirname } from "path";
184005
- import { fileURLToPath as fileURLToPath2 } from "url";
184309
+ import { existsSync as existsSync3 } from "fs";
184310
+ import { join as join4, dirname as dirname2 } from "path";
184311
+ import { fileURLToPath as fileURLToPath3 } from "url";
184006
184312
  var BUNDLED_FONT_FAMILY = "BodyFont";
184007
184313
  var BUNDLED_FONT_FAMILY_BOLD = "BodyFont-Bold";
184008
184314
  var DEFAULT_FAMILY = "BodyFont";
184009
184315
  function bundledFontPaths() {
184010
- const here = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath2(import.meta.url));
184011
- const rootFromSrc = join3(here, "..", "..");
184012
- const candidates = [join3(here, "fonts"), join3(rootFromSrc, "dist", "fonts")];
184316
+ const here = typeof __dirname !== "undefined" ? __dirname : dirname2(fileURLToPath3(import.meta.url));
184317
+ const rootFromSrc = join4(here, "..", "..");
184318
+ const candidates = [join4(here, "fonts"), join4(rootFromSrc, "dist", "fonts")];
184013
184319
  const fontsDir = candidates.find(
184014
- (d2) => existsSync2(join3(d2, "Pretendard-Regular.ttf")) && existsSync2(join3(d2, "Pretendard-Bold.ttf"))
184320
+ (d2) => existsSync3(join4(d2, "Pretendard-Regular.ttf")) && existsSync3(join4(d2, "Pretendard-Bold.ttf"))
184015
184321
  );
184016
184322
  const dir = fontsDir || candidates[0];
184017
184323
  return {
184018
- regular: join3(dir, "Pretendard-Regular.ttf"),
184019
- bold: join3(dir, "Pretendard-Bold.ttf")
184324
+ regular: join4(dir, "Pretendard-Regular.ttf"),
184325
+ bold: join4(dir, "Pretendard-Bold.ttf")
184020
184326
  };
184021
184327
  }
184022
184328
  function resolveFonts(opts) {
@@ -184029,7 +184335,7 @@ function resolveFonts(opts) {
184029
184335
  };
184030
184336
  }
184031
184337
  const paths = bundledFontPaths();
184032
- if (existsSync2(paths.regular) && existsSync2(paths.bold)) {
184338
+ if (existsSync3(paths.regular) && existsSync3(paths.bold)) {
184033
184339
  return {
184034
184340
  regularFamily: BUNDLED_FONT_FAMILY,
184035
184341
  boldFamily: BUNDLED_FONT_FAMILY_BOLD,
@@ -184051,6 +184357,21 @@ var FRONTMATTER_RE2 = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/;
184051
184357
  function stripFrontmatter2(md) {
184052
184358
  return md.replace(FRONTMATTER_RE2, "");
184053
184359
  }
184360
+ function lexAndExtractPlantUml(body) {
184361
+ const tokens = g.lexer(stripFrontmatter2(body ?? ""));
184362
+ const pumlRegistry = /* @__PURE__ */ new Map();
184363
+ let counter = 0;
184364
+ for (const tok of tokens) {
184365
+ if (tok?.type !== "code") continue;
184366
+ const lang = String(tok.lang ?? "").trim().toLowerCase();
184367
+ if (lang === "plantuml" || lang === "puml" || lang === "plantuml-markdown") {
184368
+ const id = `PUML_${counter++}`;
184369
+ pumlRegistry.set(id, String(tok.text ?? ""));
184370
+ tok._pumlId = id;
184371
+ }
184372
+ }
184373
+ return { tokens, pumlRegistry };
184374
+ }
184054
184375
  var PAGE_SIZE_DEFAULT = "A4";
184055
184376
  var MARGIN_DEFAULT = 56;
184056
184377
  async function markdownToPdf(md, opts = {}) {
@@ -184064,7 +184385,9 @@ async function markdownToPdf(md, opts = {}) {
184064
184385
  pageSize = PAGE_SIZE_DEFAULT,
184065
184386
  margin = {},
184066
184387
  language = "en",
184067
- collectWarnings = false
184388
+ collectWarnings = false,
184389
+ renderPlantUml: renderPUML = true,
184390
+ diagramDpi = 96
184068
184391
  } = opts;
184069
184392
  const top = margin.top ?? MARGIN_DEFAULT;
184070
184393
  const bottom = margin.bottom ?? MARGIN_DEFAULT;
@@ -184121,10 +184444,22 @@ async function markdownToPdf(md, opts = {}) {
184121
184444
  doc.fillColor("#000");
184122
184445
  doc.addPage();
184123
184446
  }
184124
- const body = stripFrontmatter2(md ?? "");
184125
- const tokens = g.lexer(body);
184447
+ const { tokens, pumlRegistry } = lexAndExtractPlantUml(md ?? "");
184448
+ const pumlImages = /* @__PURE__ */ new Map();
184449
+ if (renderPUML && pumlRegistry.size > 0) {
184450
+ await Promise.all(
184451
+ [...pumlRegistry].map(async ([id, source]) => {
184452
+ try {
184453
+ const diag = await renderPlantUml(source, { format: "png", dpi: diagramDpi });
184454
+ pumlImages.set(id, diag.buffer);
184455
+ } catch (err) {
184456
+ console.error(`[markdownToPdf] plantuml render failed for ${id}: ${err?.message ?? err}`);
184457
+ }
184458
+ })
184459
+ );
184460
+ }
184126
184461
  for (const tok of tokens) {
184127
- renderToken(doc, tok, fonts);
184462
+ renderToken(doc, tok, fonts, pumlImages);
184128
184463
  }
184129
184464
  if (tokens.length === 0) {
184130
184465
  doc.fontSize(11).fillColor("#000").text("");
@@ -184177,9 +184512,9 @@ async function markdownToPdf(md, opts = {}) {
184177
184512
  doc.restore();
184178
184513
  }
184179
184514
  const chunks = [];
184180
- const ended = new Promise((resolve, reject) => {
184515
+ const ended = new Promise((resolve2, reject) => {
184181
184516
  doc.on("data", (c) => chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c)));
184182
- doc.on("end", () => resolve());
184517
+ doc.on("end", () => resolve2());
184183
184518
  doc.on("error", (err) => reject(err));
184184
184519
  });
184185
184520
  doc.end();
@@ -184220,7 +184555,7 @@ function lineHeightOf(doc) {
184220
184555
  const size = doc._fontSize ?? 11;
184221
184556
  return size * 1.2;
184222
184557
  }
184223
- function renderToken(doc, tok, fonts) {
184558
+ function renderToken(doc, tok, fonts, pumlImages) {
184224
184559
  if (!tok || typeof tok !== "object") return;
184225
184560
  switch (tok.type) {
184226
184561
  case "heading":
@@ -184230,13 +184565,24 @@ function renderToken(doc, tok, fonts) {
184230
184565
  renderParagraph(doc, tok, fonts);
184231
184566
  return;
184232
184567
  case "code":
184568
+ if (tok._pumlId) {
184569
+ const buf = pumlImages.get(tok._pumlId);
184570
+ if (buf) {
184571
+ try {
184572
+ renderPlantUmlImage(doc, buf);
184573
+ return;
184574
+ } catch (err) {
184575
+ console.error(`[markdownToPdf] image() failed for ${tok._pumlId}: ${err?.message ?? err}`);
184576
+ }
184577
+ }
184578
+ }
184233
184579
  renderCodeBlock(doc, tok);
184234
184580
  return;
184235
184581
  case "blockquote":
184236
184582
  renderBlockquote(doc, tok, fonts);
184237
184583
  return;
184238
184584
  case "list":
184239
- renderList(doc, tok, fonts, 0);
184585
+ renderList(doc, tok, fonts, 0, pumlImages);
184240
184586
  return;
184241
184587
  case "table":
184242
184588
  renderTable(doc, tok, fonts);
@@ -184253,6 +184599,13 @@ function renderToken(doc, tok, fonts) {
184253
184599
  }
184254
184600
  }
184255
184601
  }
184602
+ function renderPlantUmlImage(doc, buf) {
184603
+ const marginL = doc.page.margins.left;
184604
+ const marginR = doc.page.margins.right;
184605
+ const availableWidth = doc.page.width - marginL - marginR;
184606
+ doc.y += SPACE2.CODE_BEFORE;
184607
+ doc.image(buf, { fit: [availableWidth, 200], align: "center" });
184608
+ }
184256
184609
  function renderHeading(doc, tok, fonts) {
184257
184610
  const level = Math.min(Math.max(Number(tok.depth) || 1, 1), 6);
184258
184611
  const size = HEADING_SIZES[level - 1];
@@ -184369,7 +184722,7 @@ function extractInlineText(node) {
184369
184722
  if (Array.isArray(node.tokens)) return node.tokens.map(extractInlineText).join("");
184370
184723
  return "";
184371
184724
  }
184372
- function renderList(doc, tok, fonts, depth) {
184725
+ function renderList(doc, tok, fonts, depth, pumlImages) {
184373
184726
  const items = tok.items ?? [];
184374
184727
  const ordered = !!tok.ordered;
184375
184728
  const marginL = doc.page.margins.left;
@@ -184409,9 +184762,9 @@ function renderList(doc, tok, fonts, depth) {
184409
184762
  } else if (sub?.type === "list") {
184410
184763
  doc.x = marginL + indent + 12;
184411
184764
  doc.y = doc.y + SPACE2.LIST_ITEM_BEFORE;
184412
- renderList(doc, sub, fonts, depth + 1);
184765
+ renderList(doc, sub, fonts, depth + 1, pumlImages);
184413
184766
  } else {
184414
- renderToken(doc, sub, fonts);
184767
+ renderToken(doc, sub, fonts, pumlImages);
184415
184768
  }
184416
184769
  }
184417
184770
  if (!drewAnything && item.text) {
@@ -184512,7 +184865,7 @@ function renderHr(doc) {
184512
184865
  doc.y = y2 + SPACE2.HR_AFTER;
184513
184866
  }
184514
184867
  function writePdfToTempFile(buffer) {
184515
- const path3 = join4(tmpdir(), `${randomUUID()}.pdf`);
184868
+ const path3 = join5(tmpdir(), `${randomUUID()}.pdf`);
184516
184869
  writeFileSync3(path3, buffer);
184517
184870
  return path3;
184518
184871
  }
@@ -184564,9 +184917,11 @@ var registerJiraAttachTools = (ctx2) => {
184564
184917
  font_path: external_exports.string().optional().describe("\uD3F0\uD2B8 \uD30C\uC77C \uC808\uB300 \uACBD\uB85C (.ttf/.otf/.ttc). \uBBF8\uC9C0\uC815 \uC2DC \uBC88\uB4E4 Pretendard \uC0AC\uC6A9 (\uBD80\uC7AC \uC2DC Helvetica \uD3F4\uBC31)"),
184565
184918
  font_family_name: external_exports.string().optional().describe('pdfkit registerFont \uC774\uB984 (\uAE30\uBCF8 "BodyFont")'),
184566
184919
  page_size: external_exports.enum(["A4", "LETTER", "LEGAL"]).optional().default("A4").describe("\uD398\uC774\uC9C0 \uD06C\uAE30"),
184567
- output_mode: external_exports.enum(["base64", "tmpfile"]).optional().default("base64").describe("base64(\uAE30\uBCF8, JSON \uC548\uC804) / tmpfile(\uC808\uB300\uACBD\uB85C \uBC18\uD658, consumer\uAC00 unlink \uCC45\uC784)")
184920
+ output_mode: external_exports.enum(["base64", "tmpfile"]).optional().default("base64").describe("base64(\uAE30\uBCF8, JSON \uC548\uC804) / tmpfile(\uC808\uB300\uACBD\uB85C \uBC18\uD658, consumer\uAC00 unlink \uCC45\uC784)"),
184921
+ render_plantuml: external_exports.boolean().optional().default(true).describe("Render ```plantuml fences to PNG and embed in PDF. Default true."),
184922
+ diagram_dpi: external_exports.number().optional().default(96).describe("PlantUML rendering DPI. Default 96.")
184568
184923
  },
184569
- async ({ markdown, markdown_file, title, source_ref, include_cover, author, font_path, font_family_name, page_size, output_mode }) => {
184924
+ async ({ markdown, markdown_file, title, source_ref, include_cover, author, font_path, font_family_name, page_size, output_mode, render_plantuml, diagram_dpi }) => {
184570
184925
  if (!markdown && !markdown_file) {
184571
184926
  throw new Error("Either markdown or markdown_file must be provided");
184572
184927
  }
@@ -184591,6 +184946,8 @@ var registerJiraAttachTools = (ctx2) => {
184591
184946
  ...font_path !== void 0 ? { fontPath: font_path } : {},
184592
184947
  ...font_family_name !== void 0 ? { fontFamilyName: font_family_name } : {},
184593
184948
  pageSize: page_size,
184949
+ renderPlantUml: render_plantuml,
184950
+ diagramDpi: diagram_dpi,
184594
184951
  collectWarnings: true
184595
184952
  });
184596
184953
  const buffer = result.buffer;
@@ -184685,7 +185042,9 @@ var registerJiraAttachTools = (ctx2) => {
184685
185042
  author: external_exports.string().optional().describe("PDF /Author \uBA54\uD0C0"),
184686
185043
  font_path: external_exports.string().optional().describe("\uD3F0\uD2B8 \uD30C\uC77C \uC808\uB300 \uACBD\uB85C"),
184687
185044
  font_family_name: external_exports.string().optional().describe("pdfkit registerFont \uC774\uB984"),
184688
- page_size: external_exports.enum(["A4", "LETTER", "LEGAL"]).optional().default("A4").describe("\uD398\uC774\uC9C0 \uD06C\uAE30")
185045
+ page_size: external_exports.enum(["A4", "LETTER", "LEGAL"]).optional().default("A4").describe("\uD398\uC774\uC9C0 \uD06C\uAE30"),
185046
+ render_plantuml: external_exports.boolean().optional().default(true).describe("Render ```plantuml fences to PNG and embed in PDF. Default true."),
185047
+ diagram_dpi: external_exports.number().optional().default(96).describe("PlantUML rendering DPI. Default 96.")
184689
185048
  },
184690
185049
  async ({
184691
185050
  issue_key,
@@ -184699,7 +185058,9 @@ var registerJiraAttachTools = (ctx2) => {
184699
185058
  author,
184700
185059
  font_path,
184701
185060
  font_family_name,
184702
- page_size
185061
+ page_size,
185062
+ render_plantuml,
185063
+ diagram_dpi
184703
185064
  }) => {
184704
185065
  if (!markdown && !markdown_file) {
184705
185066
  throw new Error("Either markdown or markdown_file must be provided");
@@ -184725,6 +185086,8 @@ var registerJiraAttachTools = (ctx2) => {
184725
185086
  ...font_path !== void 0 ? { fontPath: font_path } : {},
184726
185087
  ...font_family_name !== void 0 ? { fontFamilyName: font_family_name } : {},
184727
185088
  pageSize: page_size,
185089
+ renderPlantUml: render_plantuml,
185090
+ diagramDpi: diagram_dpi,
184728
185091
  collectWarnings: true
184729
185092
  });
184730
185093
  const buffer = result.buffer;
@@ -185121,9 +185484,9 @@ var registerExpertTools = (ctx2) => {
185121
185484
 
185122
185485
  // src/tools/review-criteria.ts
185123
185486
  import { readFileSync as readFileSync5 } from "fs";
185124
- import { fileURLToPath as fileURLToPath3 } from "url";
185487
+ import { fileURLToPath as fileURLToPath4 } from "url";
185125
185488
  import path2 from "path";
185126
- var __dirname3 = path2.dirname(fileURLToPath3(import.meta.url));
185489
+ var __dirname3 = path2.dirname(fileURLToPath4(import.meta.url));
185127
185490
  var CRITERIA_PATH = path2.resolve(__dirname3, "../data/review-criteria.md");
185128
185491
  var registerReviewCriteriaTools = (ctx2) => {
185129
185492
  ctx2.server.tool(
@@ -185170,7 +185533,7 @@ var xrayClientSecret = process.env.XRAY_CLIENT_SECRET || "";
185170
185533
  var xray = xrayClientId && xrayClientSecret ? new XrayClient(xrayClientId, xrayClientSecret, jiraUrl, jiraEmail, jiraToken) : null;
185171
185534
  var server = new McpServer({
185172
185535
  name: "jira-confluence",
185173
- version: "0.1.27"
185536
+ version: "0.1.29"
185174
185537
  });
185175
185538
  var ctx = { server, jira, confluence, risk, xray };
185176
185539
  registerJiraCrudTools(ctx);