@neobiotechlabs/neobiotech-dev-agent 0.1.26 → 0.1.28

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 ?? "");
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,59 @@ 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
+ const bodyBytes = Buffer.byteLength(storage, "utf8");
160088
160278
  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
160279
  if (warning) console.error(`[confluence_publish_doc] ${warning}`);
160280
+ let diagramStats = { rendered: 0, failed: 0, java_missing: false };
160281
+ const renderedDiagrams = /* @__PURE__ */ new Map();
160282
+ if (render_diagrams && pumlRegistry.size > 0) {
160283
+ for (const [id, source] of pumlRegistry) {
160284
+ try {
160285
+ const diag = await renderPlantUml(source, { format: diagram_format, dpi: diagram_dpi });
160286
+ renderedDiagrams.set(id, diag);
160287
+ diagramStats.rendered++;
160288
+ } catch (err) {
160289
+ diagramStats.failed++;
160290
+ if (/Install Java 21\+/.test(err?.message ?? "")) diagramStats.java_missing = true;
160291
+ console.error(`[confluence_publish_doc] plantuml render failed for ${id}: ${err?.message ?? err}`);
160292
+ }
160293
+ }
160294
+ }
160090
160295
  let result;
160091
160296
  let mode;
160297
+ let currentPageId;
160298
+ let currentVersion;
160299
+ let versionsUsed = 1;
160092
160300
  if (page_id) {
160093
160301
  mode = "update";
160094
160302
  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);
160303
+ currentVersion = current?.version?.number ?? 0;
160304
+ result = await confluence2.updatePage(page_id, title, storage, currentVersion);
160305
+ currentPageId = page_id;
160097
160306
  } else {
160098
160307
  mode = "create";
160099
- result = await confluence2.createPage(space_key, title, body, parent_page_id);
160308
+ result = await confluence2.createPage(space_key, title, storage, parent_page_id);
160309
+ currentPageId = result?.id;
160310
+ currentVersion = result?.version?.number ?? 1;
160311
+ }
160312
+ let finalStorage = storage;
160313
+ if (render_diagrams && renderedDiagrams.size > 0) {
160314
+ for (const [id, diag] of renderedDiagrams) {
160315
+ const filename = `diagram-${id}.${diag.format}`;
160316
+ try {
160317
+ await confluence2.addAttachment(currentPageId, filename, diag.buffer, diag.contentType);
160318
+ } catch (err) {
160319
+ console.error(`[confluence_publish_doc] attachment upload failed for ${id}: ${err?.message ?? err}`);
160320
+ renderedDiagrams.delete(id);
160321
+ diagramStats.rendered--;
160322
+ }
160323
+ }
160324
+ finalStorage = replacePumlMarkers(storage, renderedDiagrams, pumlRegistry);
160325
+ result = await confluence2.updatePage(currentPageId, title, finalStorage, currentVersion);
160326
+ versionsUsed = 2;
160100
160327
  }
160101
- const pageIdOut = result?.id ?? page_id;
160328
+ const pageIdOut = result?.id ?? currentPageId;
160102
160329
  const webui = result?._links?.webui;
160103
160330
  const url = webui ? webui.startsWith("http") ? webui : `${confluence2.getBaseUrl()}${webui}` : void 0;
160104
160331
  return {
@@ -160110,17 +160337,40 @@ var registerConfluenceTools = (ctx2) => {
160110
160337
  title,
160111
160338
  space_key,
160112
160339
  version: result?.version?.number,
160340
+ versions_used: versionsUsed,
160113
160341
  url,
160114
160342
  input_chars: md.length,
160115
160343
  input_source: markdown_file ?? "inline",
160116
- body_chars: body.length,
160117
- body_bytes: bodyBytes,
160344
+ body_chars: finalStorage.length,
160345
+ body_bytes: Buffer.byteLength(finalStorage, "utf8"),
160346
+ diagram_stats: diagramStats,
160118
160347
  ...warning ? { warning } : {}
160119
160348
  }, null, 2)
160120
160349
  }]
160121
160350
  };
160122
160351
  });
160123
160352
  };
160353
+ function replacePumlMarkers(storage, rendered, pumlRegistry) {
160354
+ let out = storage;
160355
+ for (const [id, diag] of rendered) {
160356
+ const filename = `diagram-${id}.${diag.format}`;
160357
+ const imageMacro = `<ac:image ac:alt="PlantUML Diagram ${id}"><ri:attachment ri:filename="${filename}"/></ac:image>`;
160358
+ out = out.replace(
160359
+ new RegExp(`<p data-puml-id="${id}"></p>`),
160360
+ imageMacro
160361
+ );
160362
+ }
160363
+ for (const [id, source] of pumlRegistry) {
160364
+ if (!rendered.has(id)) {
160365
+ const safeSource = source.replace(/]]>/g, "]]]]><![CDATA[>");
160366
+ out = out.replace(
160367
+ new RegExp(`<p data-puml-id="${id}"></p>`),
160368
+ `<ac:structured-macro ac:name="code"><ac:plain-text-body><![CDATA[${safeSource}]]></ac:plain-text-body></ac:structured-macro>`
160369
+ );
160370
+ }
160371
+ }
160372
+ return out;
160373
+ }
160124
160374
 
160125
160375
  // src/config/risk-models.ts
160126
160376
  var RISK_MODELS = {
@@ -161107,123 +161357,174 @@ var TOOL_CATALOG = [
161107
161357
  category: "\uBB38\uC11C \uB9AC\uBDF0"
161108
161358
  }
161109
161359
  ];
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
- };
161360
+ var WORKFLOWS = [
161361
+ {
161362
+ id: "pa",
161363
+ title: "PA Gate \uC6CC\uD06C\uD50C\uB85C\uC6B0",
161364
+ 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.",
161365
+ steps: [
161366
+ "1. iec_create_gate \u2192 PA Gate + 7\uAC1C Document \uD2F0\uCF13 \uC0DD\uC131",
161367
+ "2. [Intended Use] Document \u2192 IU \uD2F0\uCF13 \uC0DD\uC131 (\uC0AC\uC6A9\uC790, \uD658\uACBD, \uBAA9\uC801 \uC815\uC758)",
161368
+ "3. [System Requirement Spec] \u2192 SyRS \uD2F0\uCF13 \uC0DD\uC131 (IU \uAE30\uBC18 \uC2DC\uC2A4\uD15C \uC694\uAD6C\uC0AC\uD56D)",
161369
+ "4. [Classification] \u2192 \uBD84\uB958 \uD2F0\uCF13 (MDR Class \uD310\uC815)",
161370
+ "5. [SW Development Plan] \u2192 \uAC1C\uBC1C \uACC4\uD68D\uC11C \uC791\uC131",
161371
+ "6. [Risk Management Plan] \u2192 \uB9AC\uC2A4\uD06C \uAD00\uB9AC \uACC4\uD68D\uC11C \uC791\uC131",
161372
+ "7. [Security Management Plan] \u2192 \uBCF4\uC548 \uAD00\uB9AC \uACC4\uD68D\uC11C (IEC 81001-5-1)",
161373
+ "8. [Configuration Management Plan] \u2192 \uD615\uC0C1 \uAD00\uB9AC \uACC4\uD68D\uC11C",
161374
+ "",
161375
+ "\uC0AC\uC6A9\uC131 \uACF5\uD559 (IEC 62366-1)\uB3C4 PA \uB2E8\uACC4\uC5D0\uC11C \uC2DC\uC791:",
161376
+ "9. usability_create_use_spec \u2192 Use Specification \uC815\uC758",
161377
+ "10. usability_create_scenarios \u2192 Normal/Abnormal \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 \uC2DD\uBCC4"
161378
+ ],
161379
+ related: ["iec_create_gate", "usability_create_use_spec", "usability_create_scenarios"]
161380
+ },
161381
+ {
161382
+ id: "ea",
161383
+ title: "EA Gate \uC6CC\uD06C\uD50C\uB85C\uC6B0",
161384
+ 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.",
161385
+ steps: [
161386
+ "1. iec_create_gate \u2192 EA Gate + 8\uAC1C Document \uD2F0\uCF13 \uC0DD\uC131 (living documents)",
161387
+ "2. [Risk Management Report] \u2192 risk_create_hazards (FMEA) + usability_link_to_hazard",
161388
+ "3. [SW Requirements Spec] \u2192 FR/NFR \uC815\uB9AC + risk_create_mitigation",
161389
+ "4. [SW Architecture Document] \u2192 \uC544\uD0A4\uD14D\uCC98 \uACB0\uC815",
161390
+ "5. [SW Detailed Design Document] \u2192 \uC720\uB2DB \uC0C1\uC138\uC124\uACC4 + bottom-up Tasks",
161391
+ "6. [SOUP List] \u2192 doc_render(soup_list)",
161392
+ "7. [Usability Evaluation Plan] \u2192 usability_create_use_spec/scenarios \uC5F0\uACC4 + doc_render",
161393
+ "8. [Clinical Evaluation Plan] \u2192 doc_render(clinical_evaluation_plan)",
161394
+ "9. [List of Hazard-Related Use Scenarios] \u2192 usability_create_scenarios(abnormal) \uC5F0\uACC4",
161395
+ "",
161396
+ "EA 8\uAC1C \uBB38\uC11C = living document (IEC 62304 evolutionary \uBAA8\uB378):",
161397
+ " \u2022 EA: \uCD08\uAE30 \uACB0\uC815\xB7\uC2B9\uC778 / ER: \uAC1C\uBC1C\xB7\uAC80\uC99D \uC911 \uAC31\uC2E0 / CA: \uCD5C\uC885 \uD655\uC815",
161398
+ " \u2022 \uAC31\uC2E0 \uD2B8\uB9AC\uAC70:",
161399
+ " - SRS: \uC694\uAD6C\uC0AC\uD56D \uBCC0\uACBD/\uCD94\uAC00 (change-evaluation-list \uC5F0\uACC4)",
161400
+ " - SAD: \uC544\uD0A4\uD14D\uCC98 \uBCC0\uACBD/\uC0C1\uC138\uD654 (architecture-checklist)",
161401
+ " - SDD: sprint \uC720\uB2DB \uCD94\uAC00/\uC0C1\uC138\uD654",
161402
+ " - FMEA Risk Table: \uC0C8 \uC704\uD5D8\xB7\uD1B5\uC81C \uC2DD\uBCC4 (hazard \uCD94\uAC00)",
161403
+ " - SOUP List: \uC758\uC874\uC131 \uBCC0\uACBD (\u2192 CA \uCD5C\uC885 SBOM \uAE30\uBC18 \uD655\uC815)",
161404
+ " - Usability/Clinical Eval Plan: \uD3C9\uAC00 \uACB0\uACFC \uBC18\uC601\xB7\uACC4\uD68D \uC870\uC815",
161405
+ " - Hazard-Related Use Scenarios: \uC0C8 \uC2DC\uB098\uB9AC\uC624 \uC2DD\uBCC4",
161406
+ "",
161407
+ "\uAC80\uC99D:",
161408
+ "10. xray_create_test \u2192 \uD14C\uC2A4\uD2B8 \uCF00\uC774\uC2A4 \uC0DD\uC131",
161409
+ "11. usability_create_validation \u2192 \uC0AC\uC6A9\uC131 \uAC80\uC99D",
161410
+ "12. doc_check_completeness \u2192 \uAE30\uC220 \uBB38\uC11C \uC644\uC804\uC131 \uAC80\uC99D",
161411
+ "13. doc_generate_evidence \u2192 NB \uC2EC\uC0AC\uC6A9 \uC99D\uAC70 \uD328\uD0A4\uC9C0 \uC0DD\uC131"
161412
+ ],
161413
+ related: ["iec_create_gate", "risk_create_hazards", "risk_create_mitigation", "doc_render", "xray_create_test", "usability_create_validation", "doc_check_completeness", "doc_generate_evidence"]
161414
+ },
161415
+ {
161416
+ id: "risk",
161417
+ title: "\uB9AC\uC2A4\uD06C \uAD00\uB9AC \uC6CC\uD06C\uD50C\uB85C\uC6B0 (3 \uBAA8\uB378 \uBA40\uD2F0\uC785\uB825, 0.1.21+)",
161418
+ 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.",
161419
+ steps: [
161420
+ "\uB3C4\uAD6C \uC778\uD130\uD398\uC774\uC2A4:",
161421
+ "- 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)",
161422
+ "- \uC635\uC158\uC740 name \uBB38\uC790\uC5F4 \uC785\uB825 \u2014 \uB3C4\uAD6C\uAC00 UUID \uC790\uB3D9 \uB9E4\uD551 (SoftComply RMP \uACF5\uC2DD schema)",
161423
+ "- lookup \uC2E4\uD328 \uC635\uC158\uC740 SKIPPED \uD45C\uC2DC, \uB098\uBA38\uC9C0 \uC815\uC0C1 \uC9C4\uD589",
161424
+ "",
161425
+ "1. risk_get_config \u2192 \uD504\uB85C\uC81D\uD2B8\uC758 3 \uBAA8\uB378 \uC815\uC758 + \uC635\uC158 name \uBAA9\uB85D \uC870\uD68C",
161426
+ "2. IU/SyRS \uBD84\uC11D \u2192 Hazard \uC2DD\uBCC4 (P1-P2 ISO 14971)",
161427
+ "3. risk_create_hazards \u2192 Hazard \uC77C\uAD04 \uC0DD\uC131 + 3 \uBAA8\uB378 risk values \uC790\uB3D9 \uC14B\uD305 + Risk Source/Relates \uB9C1\uD06C",
161428
+ " - 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",
161429
+ "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)",
161430
+ "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)",
161431
+ "6. RMP Panel\uC5D0\uC11C \uC9C1\uC811 \uC785\uB825\uD558\uB294 \uBAA8\uB378 (Risk Score Calculation / Cyber Sec Risk Model) \uC740",
161432
+ " 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",
161433
+ "7. traceability_check \u2192 \uCD94\uC801\uC131 \uD655\uC778",
161434
+ "8. traceability_find_missing \u2192 \uB204\uB77D \uB9C1\uD06C \uAC80\uC0C9",
161435
+ "9. traceability_fix \u2192 \uB204\uB77D \uB9C1\uD06C \uC790\uB3D9 \uC0DD\uC131"
161436
+ ],
161437
+ related: ["risk_get_config", "risk_create_hazards", "risk_set_values", "risk_create_mitigation", "traceability_check", "traceability_find_missing", "traceability_fix"]
161438
+ },
161439
+ {
161440
+ id: "usability",
161441
+ title: "\uC0AC\uC6A9\uC131 \uACF5\uD559 (IEC 62366-1) \uC6CC\uD06C\uD50C\uB85C\uC6B0",
161442
+ description: "Use Specification \u2192 Normal/Abnormal \uC2DC\uB098\uB9AC\uC624 \u2192 Hazard \uC5F0\uACB0(Risk Source) \u2192 \uAC80\uC99D \uACB0\uACFC \uAE30\uB85D.",
161443
+ steps: [
161444
+ "1. usability_create_use_spec \u2192 Use Specification \uC815\uC758",
161445
+ " (\uC0AC\uC6A9\uC790 \uD504\uB85C\uD30C\uC77C, \uC0AC\uC6A9 \uD658\uACBD, \uC758\uB3C4\uB41C \uC0AC\uC6A9)",
161446
+ "2. usability_create_scenarios \u2192 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 \uC2DD\uBCC4",
161447
+ " - Normal Use: \uC815\uC0C1 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624",
161448
+ " - Abnormal Use: \uBE44\uC815\uC0C1 \uC0AC\uC6A9 \uC2DC\uB098\uB9AC\uC624 (Hazard \uC6D0\uCC9C)",
161449
+ "3. usability_link_to_hazard \u2192 \uC2DC\uB098\uB9AC\uC624 \u2192 Hazard \uC5F0\uACB0",
161450
+ " (Risk Source \uB9C1\uD06C\uB85C \uC790\uB3D9 \uC5F0\uACB0)",
161451
+ "4. usability_create_validation \u2192 \uC0AC\uC6A9\uC131 \uAC80\uC99D \uACB0\uACFC \uAE30\uB85D",
161452
+ " (\uAC80\uC99D \uBC29\uBC95, \uACB0\uACFC, \uC99D\uBE59)"
161453
+ ],
161454
+ related: ["usability_create_use_spec", "usability_create_scenarios", "usability_link_to_hazard", "usability_create_validation"]
161455
+ },
161456
+ {
161457
+ id: "mdr",
161458
+ title: "MDR \uAE30\uC220 \uBB38\uC11C (Annex II) \uC6CC\uD06C\uD50C\uB85C\uC6B0",
161459
+ 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.",
161460
+ steps: [
161461
+ "1. doc_create_from_template \u2192 Annex II \uAD6C\uC870\uB85C \uBB38\uC11C \uD2F0\uCF13 \uC0DD\uC131",
161462
+ "2. \uAC01 \uC139\uC158\uBCC4 \uB0B4\uC6A9 \uC791\uC131",
161463
+ "3. doc_check_completeness \u2192 \uB204\uB77D/\uBBF8\uC2B9\uC778 \uD56D\uBAA9 \uAC80\uC0AC",
161464
+ "4. doc_generate_evidence \u2192 \uC99D\uAC70 \uD328\uD0A4\uC9C0 \uC0DD\uC131",
161465
+ " - \uCD94\uC801\uC131 \uB9E4\uD2B8\uB9AD\uC2A4 (IU \u2192 SyRS \u2192 Req \u2192 DD \u2192 Task \u2192 Test)",
161466
+ " - \uB9AC\uC2A4\uD06C \uB9E4\uD2B8\uB9AD\uC2A4 (Hazard \xD7 Severity \xD7 P1/P2)",
161467
+ " - V&V \uC694\uC57D (\uD14C\uC2A4\uD2B8 \uD1B5\uACFC\uC728)"
161468
+ ],
161469
+ related: ["doc_create_from_template", "doc_check_completeness", "doc_generate_evidence"]
161470
+ },
161471
+ {
161472
+ id: "customfield",
161473
+ title: "customfield / \uBB38\uC11C \uD544\uB4DC \uCC44\uC6B0\uAE30 \uC6CC\uD06C\uD50C\uB85C\uC6B0",
161474
+ 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.',
161475
+ steps: [
161476
+ "\uC8FC\uC758: customfield \uC911 \uC77C\uBD80\uB294 rich text(ADF)\uB97C \uC694\uAD6C\uD558\uC9C0\uB9CC, createmeta\uAC00 string\uC73C\uB85C",
161477
+ '\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.',
161478
+ "",
161479
+ "1. jira_get_issue_type_schema \u2192 \uC774\uC288\uD0C0\uC785 \uD544\uB4DC \uAD6C\uC870 + inferredFormat \uD655\uC778",
161480
+ " inferredFormat \uAC12: adf(\uD655\uC815) / adf-suspect(string\uC774\uB098 textarea, \uC758\uC2EC) /",
161481
+ " option / string / number / date ...",
161482
+ "2. ADF \uD544\uB4DC(customfield_*) \uC2DD\uBCC4 \u2014 inferredFormat\uC774 adf \uB610\uB294 adf-suspect\uC778 \uD544\uB4DC",
161483
+ "3. jira_create_issue / jira_update_issue \uD638\uCD9C \uC2DC:",
161484
+ " - \uC790\uB3D9 \uAC10\uC9C0: auto_detect_rich_text: true (schema\uC5D0\uC11C ADF \uD544\uB4DC \uC790\uB3D9 \uCD94\uC815)",
161485
+ ' - \uBA85\uC2DC \uC9C0\uC815: rich_text_custom_fields: ["customfield_xxxx", ...] (\uCD5C\uC885 \uBCF4\uC218)',
161486
+ " - \uD3C9\uBB38\uC744 \uB118\uAE30\uBA74 \uC790\uB3D9\uC73C\uB85C ADF\uB85C \uBCC0\uD658\uB428 (\uC774\uBBF8 ADF \uAC1D\uCCB4\uBA74 \uADF8\uB300\uB85C)",
161487
+ '4. option \uD544\uB4DC(\uC608: Phase)\uB294 { value: "PA" } \uD615\uD0DC\uB85C extra_fields/fields\uC5D0 \uC804\uB2EC',
161488
+ "",
161489
+ "\uD45C\uC900 \uC6D0\uCE59: \uBB38\uC11C \uD2F0\uCF13\uC744 \uB9CC\uB4E4 \uB54C\uB294 \uBC18\uB4DC\uC2DC schema\uB97C \uBA3C\uC800 \uBCF4\uACE0,",
161490
+ "customfield\uB97C \uADF8 \uAD6C\uC870\uC5D0 \uB9DE\uCDB0 \uCC44\uC6B4\uB2E4 \u2014 \uBE48 \uD2F0\uCF13\uC744 \uBC29\uCE58\uD558\uC9C0 \uC54A\uB294\uB2E4."
161491
+ ],
161492
+ related: ["jira_get_issue_type_schema", "jira_create_issue", "jira_update_issue"]
161493
+ },
161494
+ {
161495
+ id: "diagram",
161496
+ title: "PlantUML \uB2E4\uC774\uC5B4\uADF8\uB7A8 \uB80C\uB354\uB9C1",
161497
+ 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).",
161498
+ steps: [
161499
+ "1. Java 21+ \uC124\uCE58 \uD655\uC778: java -version (\uC5C6\uC73C\uBA74 https://adoptium.net/)",
161500
+ "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.",
161501
+ "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.",
161502
+ "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.",
161503
+ "5. \uC635\uD2B8\uC544\uC6C3: render_diagrams: false \uB610\uB294 render_plantuml: false. \uBD80\uBD84 disable \uAC00\uB2A5.",
161504
+ "6. \uACB0\uACFC JSON\uC758 diagram_stats (rendered/failed/java_missing) \uD544\uB4DC\uB85C \uB80C\uB354 \uACB0\uACFC \uD655\uC778."
161505
+ ],
161506
+ related: ["confluence_publish_doc", "markdown_to_pdf", "jira_attach_pdf_from_markdown"]
161507
+ }
161508
+ ];
161509
+ function workflowToText(w2) {
161510
+ const lines = [];
161511
+ lines.push(`=== ${w2.title} ===`);
161512
+ lines.push("");
161513
+ if (w2.description) {
161514
+ lines.push(w2.description);
161515
+ lines.push("");
161516
+ }
161517
+ for (const step of w2.steps) {
161518
+ lines.push(step);
161519
+ }
161520
+ return lines.join("\n");
161521
+ }
161221
161522
 
161222
161523
  // src/tools/help.ts
161223
161524
  var registerHelpTools = ({ server: server2 }) => {
161224
161525
  server2.tool("help", "Show available tools, workflows, and usage guides. Call without arguments for full catalog, or specify a topic.", {
161225
161526
  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")'
161527
+ '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
161528
  )
161228
161529
  }, async ({ topic }) => {
161229
161530
  if (!topic) {
@@ -161236,19 +161537,21 @@ var registerHelpTools = ({ server: server2 }) => {
161236
161537
  }
161237
161538
  lines.push("");
161238
161539
  }
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")');
161540
+ const wfIds2 = WORKFLOWS.map((w2) => w2.id).join(", ");
161541
+ 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
161542
  lines.push('\uD2B9\uC815 \uD234 \uC0C1\uC138: help(topic="\uD234\uC774\uB984")');
161241
161543
  return { content: [{ type: "text", text: lines.join("\n") }] };
161242
161544
  }
161243
- if (WORKFLOWS[topic]) {
161244
- return { content: [{ type: "text", text: WORKFLOWS[topic] }] };
161545
+ const workflow = WORKFLOWS.find((w2) => w2.id === topic);
161546
+ if (workflow) {
161547
+ return { content: [{ type: "text", text: workflowToText(workflow) }] };
161245
161548
  }
161246
161549
  if (topic === "tools") {
161247
161550
  const lines = TOOL_CATALOG.map((t2) => `${t2.name} [${t2.category}]: ${t2.description}`);
161248
161551
  return { content: [{ type: "text", text: lines.join("\n") }] };
161249
161552
  }
161250
161553
  if (topic === "workflows") {
161251
- const lines = Object.values(WORKFLOWS).join("\n\n");
161554
+ const lines = WORKFLOWS.map(workflowToText).join("\n\n");
161252
161555
  return { content: [{ type: "text", text: lines }] };
161253
161556
  }
161254
161557
  const tool = TOOL_CATALOG.find((t2) => t2.name === topic);
@@ -161258,8 +161561,9 @@ ${tool.description}
161258
161561
 
161259
161562
  \uC790\uC138\uD55C \uD30C\uB77C\uBBF8\uD130\uB294 \uC774 \uD234\uC744 \uC9C1\uC811 \uD638\uCD9C\uD558\uC5EC \uD655\uC778\uD558\uC138\uC694.` }] };
161260
161563
  }
161564
+ const wfIds = WORKFLOWS.map((w2) => w2.id).join(", ");
161261
161565
  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` }] };
161566
+ \uC0AC\uC6A9 \uAC00\uB2A5: tools, workflows, ${wfIds}, \uB610\uB294 \uD234 \uC774\uB984` }] };
161263
161567
  });
161264
161568
  };
161265
161569
 
@@ -161747,8 +162051,8 @@ var registerDocValidationTools = (ctx2) => {
161747
162051
  };
161748
162052
 
161749
162053
  // src/tools/init.ts
161750
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync } from "fs";
161751
- import { join as join2 } from "path";
162054
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
162055
+ import { join as join3 } from "path";
161752
162056
  var registerInitTools = ({ server: server2 }) => {
161753
162057
  server2.tool(
161754
162058
  "jira_init_project",
@@ -161758,11 +162062,11 @@ var registerInitTools = ({ server: server2 }) => {
161758
162062
  },
161759
162063
  async ({ project_key }) => {
161760
162064
  const cwd = process.cwd();
161761
- const claudeMdPath = join2(cwd, "CLAUDE.md");
162065
+ const claudeMdPath = join3(cwd, "CLAUDE.md");
161762
162066
  const line = `\uC774 \uD504\uB85C\uC81D\uD2B8\uC758 Jira key\uB294 ${project_key}\uC785\uB2C8\uB2E4.`;
161763
162067
  try {
161764
162068
  let content = "";
161765
- if (existsSync(claudeMdPath)) {
162069
+ if (existsSync2(claudeMdPath)) {
161766
162070
  content = readFileSync2(claudeMdPath, "utf-8");
161767
162071
  if (content.includes(line)) {
161768
162072
  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 +162093,7 @@ var registerInitTools = ({ server: server2 }) => {
161789
162093
 
161790
162094
  // src/tools/doc-render.ts
161791
162095
  import { readFileSync as readFileSync3 } from "fs";
161792
- import { fileURLToPath } from "url";
162096
+ import { fileURLToPath as fileURLToPath2 } from "url";
161793
162097
  import path from "path";
161794
162098
 
161795
162099
  // src/utils/doc-render.ts
@@ -161854,7 +162158,7 @@ var TEMPLATE_VERSION = "17d0c4e";
161854
162158
 
161855
162159
  // src/tools/doc-render.ts
161856
162160
  init_adf();
161857
- var __dirname2 = path.dirname(fileURLToPath(import.meta.url));
162161
+ var __dirname2 = path.dirname(fileURLToPath2(import.meta.url));
161858
162162
  var TEMPLATE_DIR = path.resolve(__dirname2, "../vendor/dev-docs-templates/templates");
161859
162163
  var registerDocRenderTools = (ctx2) => {
161860
162164
  const { server: server2, jira: jira2 } = ctx2;
@@ -161949,7 +162253,7 @@ import { execFileSync } from "child_process";
161949
162253
  // src/utils/markdownToPdf.ts
161950
162254
  import { writeFileSync as writeFileSync3 } from "fs";
161951
162255
  import { tmpdir } from "os";
161952
- import { join as join4 } from "path";
162256
+ import { join as join5 } from "path";
161953
162257
  import { randomUUID } from "crypto";
161954
162258
 
161955
162259
  // node_modules/pdfkit/js/pdfkit.es.js
@@ -163841,12 +164145,12 @@ function $e71565f2ce09cb6b$export$69a3209f1a06c04d(target, key, descriptor) {
163841
164145
  let fn = descriptor.value;
163842
164146
  return {
163843
164147
  get() {
163844
- let cache = /* @__PURE__ */ new Map();
164148
+ let cache2 = /* @__PURE__ */ new Map();
163845
164149
  function memoized(...args) {
163846
164150
  let key2 = args.length > 0 ? args[0] : "value";
163847
- if (cache.has(key2)) return cache.get(key2);
164151
+ if (cache2.has(key2)) return cache2.get(key2);
163848
164152
  let result = fn.apply(this, args);
163849
- cache.set(key2, result);
164153
+ cache2.set(key2, result);
163850
164154
  return result;
163851
164155
  }
163852
164156
  Object.defineProperty(this, key, {
@@ -170517,8 +170821,8 @@ var $55f71433a605c87d$export$2e2bcd8739ae039 = class {
170517
170821
  }
170518
170822
  if (count === 1) {
170519
170823
  let result = input.map((g3) => g3.id);
170520
- let cache = this.inputCache[found];
170521
- if (cache) cache.push(result);
170824
+ let cache2 = this.inputCache[found];
170825
+ if (cache2) cache2.push(result);
170522
170826
  else this.inputCache[found] = [
170523
170827
  result
170524
170828
  ];
@@ -182989,14 +183293,14 @@ var SubsetMixin = {
182989
183293
  var ROW_FIELDS = ["height", "minHeight", "maxHeight"];
182990
183294
  var COLUMN_FIELDS = ["width", "minWidth", "maxWidth"];
182991
183295
  function memoize(fn, maxSize) {
182992
- const cache = /* @__PURE__ */ new Map();
183296
+ const cache2 = /* @__PURE__ */ new Map();
182993
183297
  return function(...args) {
182994
183298
  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());
183299
+ if (!cache2.has(key)) {
183300
+ cache2.set(key, fn(...args));
183301
+ if (cache2.size > maxSize) cache2.delete(cache2.keys().next());
182998
183302
  }
182999
- return cache.get(key);
183303
+ return cache2.get(key);
183000
183304
  };
183001
183305
  }
183002
183306
  function isObject2(item) {
@@ -184000,23 +184304,23 @@ PDFDocument.LineWrapper = LineWrapper;
184000
184304
  init_marked_esm();
184001
184305
 
184002
184306
  // 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";
184307
+ import { existsSync as existsSync3 } from "fs";
184308
+ import { join as join4, dirname as dirname2 } from "path";
184309
+ import { fileURLToPath as fileURLToPath3 } from "url";
184006
184310
  var BUNDLED_FONT_FAMILY = "BodyFont";
184007
184311
  var BUNDLED_FONT_FAMILY_BOLD = "BodyFont-Bold";
184008
184312
  var DEFAULT_FAMILY = "BodyFont";
184009
184313
  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")];
184314
+ const here = typeof __dirname !== "undefined" ? __dirname : dirname2(fileURLToPath3(import.meta.url));
184315
+ const rootFromSrc = join4(here, "..", "..");
184316
+ const candidates = [join4(here, "fonts"), join4(rootFromSrc, "dist", "fonts")];
184013
184317
  const fontsDir = candidates.find(
184014
- (d2) => existsSync2(join3(d2, "Pretendard-Regular.ttf")) && existsSync2(join3(d2, "Pretendard-Bold.ttf"))
184318
+ (d2) => existsSync3(join4(d2, "Pretendard-Regular.ttf")) && existsSync3(join4(d2, "Pretendard-Bold.ttf"))
184015
184319
  );
184016
184320
  const dir = fontsDir || candidates[0];
184017
184321
  return {
184018
- regular: join3(dir, "Pretendard-Regular.ttf"),
184019
- bold: join3(dir, "Pretendard-Bold.ttf")
184322
+ regular: join4(dir, "Pretendard-Regular.ttf"),
184323
+ bold: join4(dir, "Pretendard-Bold.ttf")
184020
184324
  };
184021
184325
  }
184022
184326
  function resolveFonts(opts) {
@@ -184029,7 +184333,7 @@ function resolveFonts(opts) {
184029
184333
  };
184030
184334
  }
184031
184335
  const paths = bundledFontPaths();
184032
- if (existsSync2(paths.regular) && existsSync2(paths.bold)) {
184336
+ if (existsSync3(paths.regular) && existsSync3(paths.bold)) {
184033
184337
  return {
184034
184338
  regularFamily: BUNDLED_FONT_FAMILY,
184035
184339
  boldFamily: BUNDLED_FONT_FAMILY_BOLD,
@@ -184051,6 +184355,21 @@ var FRONTMATTER_RE2 = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/;
184051
184355
  function stripFrontmatter2(md) {
184052
184356
  return md.replace(FRONTMATTER_RE2, "");
184053
184357
  }
184358
+ function lexAndExtractPlantUml(body) {
184359
+ const tokens = g.lexer(stripFrontmatter2(body ?? ""));
184360
+ const pumlRegistry = /* @__PURE__ */ new Map();
184361
+ let counter = 0;
184362
+ for (const tok of tokens) {
184363
+ if (tok?.type !== "code") continue;
184364
+ const lang = String(tok.lang ?? "").trim().toLowerCase();
184365
+ if (lang === "plantuml" || lang === "puml" || lang === "plantuml-markdown") {
184366
+ const id = `PUML_${counter++}`;
184367
+ pumlRegistry.set(id, String(tok.text ?? ""));
184368
+ tok._pumlId = id;
184369
+ }
184370
+ }
184371
+ return { tokens, pumlRegistry };
184372
+ }
184054
184373
  var PAGE_SIZE_DEFAULT = "A4";
184055
184374
  var MARGIN_DEFAULT = 56;
184056
184375
  async function markdownToPdf(md, opts = {}) {
@@ -184064,7 +184383,9 @@ async function markdownToPdf(md, opts = {}) {
184064
184383
  pageSize = PAGE_SIZE_DEFAULT,
184065
184384
  margin = {},
184066
184385
  language = "en",
184067
- collectWarnings = false
184386
+ collectWarnings = false,
184387
+ renderPlantUml: renderPUML = true,
184388
+ diagramDpi = 96
184068
184389
  } = opts;
184069
184390
  const top = margin.top ?? MARGIN_DEFAULT;
184070
184391
  const bottom = margin.bottom ?? MARGIN_DEFAULT;
@@ -184121,10 +184442,22 @@ async function markdownToPdf(md, opts = {}) {
184121
184442
  doc.fillColor("#000");
184122
184443
  doc.addPage();
184123
184444
  }
184124
- const body = stripFrontmatter2(md ?? "");
184125
- const tokens = g.lexer(body);
184445
+ const { tokens, pumlRegistry } = lexAndExtractPlantUml(md ?? "");
184446
+ const pumlImages = /* @__PURE__ */ new Map();
184447
+ if (renderPUML && pumlRegistry.size > 0) {
184448
+ await Promise.all(
184449
+ [...pumlRegistry].map(async ([id, source]) => {
184450
+ try {
184451
+ const diag = await renderPlantUml(source, { format: "png", dpi: diagramDpi });
184452
+ pumlImages.set(id, diag.buffer);
184453
+ } catch (err) {
184454
+ console.error(`[markdownToPdf] plantuml render failed for ${id}: ${err?.message ?? err}`);
184455
+ }
184456
+ })
184457
+ );
184458
+ }
184126
184459
  for (const tok of tokens) {
184127
- renderToken(doc, tok, fonts);
184460
+ renderToken(doc, tok, fonts, pumlImages);
184128
184461
  }
184129
184462
  if (tokens.length === 0) {
184130
184463
  doc.fontSize(11).fillColor("#000").text("");
@@ -184177,9 +184510,9 @@ async function markdownToPdf(md, opts = {}) {
184177
184510
  doc.restore();
184178
184511
  }
184179
184512
  const chunks = [];
184180
- const ended = new Promise((resolve, reject) => {
184513
+ const ended = new Promise((resolve2, reject) => {
184181
184514
  doc.on("data", (c) => chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c)));
184182
- doc.on("end", () => resolve());
184515
+ doc.on("end", () => resolve2());
184183
184516
  doc.on("error", (err) => reject(err));
184184
184517
  });
184185
184518
  doc.end();
@@ -184220,7 +184553,7 @@ function lineHeightOf(doc) {
184220
184553
  const size = doc._fontSize ?? 11;
184221
184554
  return size * 1.2;
184222
184555
  }
184223
- function renderToken(doc, tok, fonts) {
184556
+ function renderToken(doc, tok, fonts, pumlImages) {
184224
184557
  if (!tok || typeof tok !== "object") return;
184225
184558
  switch (tok.type) {
184226
184559
  case "heading":
@@ -184230,13 +184563,24 @@ function renderToken(doc, tok, fonts) {
184230
184563
  renderParagraph(doc, tok, fonts);
184231
184564
  return;
184232
184565
  case "code":
184566
+ if (tok._pumlId) {
184567
+ const buf = pumlImages.get(tok._pumlId);
184568
+ if (buf) {
184569
+ try {
184570
+ renderPlantUmlImage(doc, buf);
184571
+ return;
184572
+ } catch (err) {
184573
+ console.error(`[markdownToPdf] image() failed for ${tok._pumlId}: ${err?.message ?? err}`);
184574
+ }
184575
+ }
184576
+ }
184233
184577
  renderCodeBlock(doc, tok);
184234
184578
  return;
184235
184579
  case "blockquote":
184236
184580
  renderBlockquote(doc, tok, fonts);
184237
184581
  return;
184238
184582
  case "list":
184239
- renderList(doc, tok, fonts, 0);
184583
+ renderList(doc, tok, fonts, 0, pumlImages);
184240
184584
  return;
184241
184585
  case "table":
184242
184586
  renderTable(doc, tok, fonts);
@@ -184253,6 +184597,13 @@ function renderToken(doc, tok, fonts) {
184253
184597
  }
184254
184598
  }
184255
184599
  }
184600
+ function renderPlantUmlImage(doc, buf) {
184601
+ const marginL = doc.page.margins.left;
184602
+ const marginR = doc.page.margins.right;
184603
+ const availableWidth = doc.page.width - marginL - marginR;
184604
+ doc.y += SPACE2.CODE_BEFORE;
184605
+ doc.image(buf, { fit: [availableWidth, 200], align: "center" });
184606
+ }
184256
184607
  function renderHeading(doc, tok, fonts) {
184257
184608
  const level = Math.min(Math.max(Number(tok.depth) || 1, 1), 6);
184258
184609
  const size = HEADING_SIZES[level - 1];
@@ -184369,7 +184720,7 @@ function extractInlineText(node) {
184369
184720
  if (Array.isArray(node.tokens)) return node.tokens.map(extractInlineText).join("");
184370
184721
  return "";
184371
184722
  }
184372
- function renderList(doc, tok, fonts, depth) {
184723
+ function renderList(doc, tok, fonts, depth, pumlImages) {
184373
184724
  const items = tok.items ?? [];
184374
184725
  const ordered = !!tok.ordered;
184375
184726
  const marginL = doc.page.margins.left;
@@ -184409,9 +184760,9 @@ function renderList(doc, tok, fonts, depth) {
184409
184760
  } else if (sub?.type === "list") {
184410
184761
  doc.x = marginL + indent + 12;
184411
184762
  doc.y = doc.y + SPACE2.LIST_ITEM_BEFORE;
184412
- renderList(doc, sub, fonts, depth + 1);
184763
+ renderList(doc, sub, fonts, depth + 1, pumlImages);
184413
184764
  } else {
184414
- renderToken(doc, sub, fonts);
184765
+ renderToken(doc, sub, fonts, pumlImages);
184415
184766
  }
184416
184767
  }
184417
184768
  if (!drewAnything && item.text) {
@@ -184512,7 +184863,7 @@ function renderHr(doc) {
184512
184863
  doc.y = y2 + SPACE2.HR_AFTER;
184513
184864
  }
184514
184865
  function writePdfToTempFile(buffer) {
184515
- const path3 = join4(tmpdir(), `${randomUUID()}.pdf`);
184866
+ const path3 = join5(tmpdir(), `${randomUUID()}.pdf`);
184516
184867
  writeFileSync3(path3, buffer);
184517
184868
  return path3;
184518
184869
  }
@@ -184564,9 +184915,11 @@ var registerJiraAttachTools = (ctx2) => {
184564
184915
  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
184916
  font_family_name: external_exports.string().optional().describe('pdfkit registerFont \uC774\uB984 (\uAE30\uBCF8 "BodyFont")'),
184566
184917
  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)")
184918
+ 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)"),
184919
+ render_plantuml: external_exports.boolean().optional().default(true).describe("Render ```plantuml fences to PNG and embed in PDF. Default true."),
184920
+ diagram_dpi: external_exports.number().optional().default(96).describe("PlantUML rendering DPI. Default 96.")
184568
184921
  },
184569
- async ({ markdown, markdown_file, title, source_ref, include_cover, author, font_path, font_family_name, page_size, output_mode }) => {
184922
+ async ({ markdown, markdown_file, title, source_ref, include_cover, author, font_path, font_family_name, page_size, output_mode, render_plantuml, diagram_dpi }) => {
184570
184923
  if (!markdown && !markdown_file) {
184571
184924
  throw new Error("Either markdown or markdown_file must be provided");
184572
184925
  }
@@ -184591,6 +184944,8 @@ var registerJiraAttachTools = (ctx2) => {
184591
184944
  ...font_path !== void 0 ? { fontPath: font_path } : {},
184592
184945
  ...font_family_name !== void 0 ? { fontFamilyName: font_family_name } : {},
184593
184946
  pageSize: page_size,
184947
+ renderPlantUml: render_plantuml,
184948
+ diagramDpi: diagram_dpi,
184594
184949
  collectWarnings: true
184595
184950
  });
184596
184951
  const buffer = result.buffer;
@@ -184685,7 +185040,9 @@ var registerJiraAttachTools = (ctx2) => {
184685
185040
  author: external_exports.string().optional().describe("PDF /Author \uBA54\uD0C0"),
184686
185041
  font_path: external_exports.string().optional().describe("\uD3F0\uD2B8 \uD30C\uC77C \uC808\uB300 \uACBD\uB85C"),
184687
185042
  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")
185043
+ page_size: external_exports.enum(["A4", "LETTER", "LEGAL"]).optional().default("A4").describe("\uD398\uC774\uC9C0 \uD06C\uAE30"),
185044
+ render_plantuml: external_exports.boolean().optional().default(true).describe("Render ```plantuml fences to PNG and embed in PDF. Default true."),
185045
+ diagram_dpi: external_exports.number().optional().default(96).describe("PlantUML rendering DPI. Default 96.")
184689
185046
  },
184690
185047
  async ({
184691
185048
  issue_key,
@@ -184699,7 +185056,9 @@ var registerJiraAttachTools = (ctx2) => {
184699
185056
  author,
184700
185057
  font_path,
184701
185058
  font_family_name,
184702
- page_size
185059
+ page_size,
185060
+ render_plantuml,
185061
+ diagram_dpi
184703
185062
  }) => {
184704
185063
  if (!markdown && !markdown_file) {
184705
185064
  throw new Error("Either markdown or markdown_file must be provided");
@@ -184725,6 +185084,8 @@ var registerJiraAttachTools = (ctx2) => {
184725
185084
  ...font_path !== void 0 ? { fontPath: font_path } : {},
184726
185085
  ...font_family_name !== void 0 ? { fontFamilyName: font_family_name } : {},
184727
185086
  pageSize: page_size,
185087
+ renderPlantUml: render_plantuml,
185088
+ diagramDpi: diagram_dpi,
184728
185089
  collectWarnings: true
184729
185090
  });
184730
185091
  const buffer = result.buffer;
@@ -185121,9 +185482,9 @@ var registerExpertTools = (ctx2) => {
185121
185482
 
185122
185483
  // src/tools/review-criteria.ts
185123
185484
  import { readFileSync as readFileSync5 } from "fs";
185124
- import { fileURLToPath as fileURLToPath3 } from "url";
185485
+ import { fileURLToPath as fileURLToPath4 } from "url";
185125
185486
  import path2 from "path";
185126
- var __dirname3 = path2.dirname(fileURLToPath3(import.meta.url));
185487
+ var __dirname3 = path2.dirname(fileURLToPath4(import.meta.url));
185127
185488
  var CRITERIA_PATH = path2.resolve(__dirname3, "../data/review-criteria.md");
185128
185489
  var registerReviewCriteriaTools = (ctx2) => {
185129
185490
  ctx2.server.tool(
@@ -185170,7 +185531,7 @@ var xrayClientSecret = process.env.XRAY_CLIENT_SECRET || "";
185170
185531
  var xray = xrayClientId && xrayClientSecret ? new XrayClient(xrayClientId, xrayClientSecret, jiraUrl, jiraEmail, jiraToken) : null;
185171
185532
  var server = new McpServer({
185172
185533
  name: "jira-confluence",
185173
- version: "0.1.26"
185534
+ version: "0.1.28"
185174
185535
  });
185175
185536
  var ctx = { server, jira, confluence, risk, xray };
185176
185537
  registerJiraCrudTools(ctx);