@neobiotechlabs/neobiotech-dev-agent 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@
10
10
  "source": "npm",
11
11
  "package": "@neobiotechlabs/neobiotech-dev-agent",
12
12
  "registry": "https://registry.npmjs.org",
13
- "version": "0.1.20"
13
+ "version": "0.1.21"
14
14
  },
15
15
  "description": "의료기기 SW 규제 개발 자동화 (Jira·Confluence·Risk·CVSS·Xray·IEC 62304/62366·MDR·문서 렌더)",
16
16
  "category": "medical-device",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neobiotech-dev-agent",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "displayName": "NeoBiotech Dev Agent (MedDev)",
5
5
  "description": "의료기기 SW 규제 개발 자동화 — Jira·Confluence·Risk·CVSS·Xray·IEC 62304/62366·MDR·문서 렌더",
6
6
  "author": {
Binary file
Binary file
Binary file
package/dist/index.js CHANGED
@@ -159642,6 +159642,7 @@ var RiskClient = class {
159642
159642
  this.authHeader = `Basic ${Buffer.from(`${email2}:${token}`).toString("base64")}`;
159643
159643
  this.riskAuthToken = riskAuthToken2;
159644
159644
  }
159645
+ /** Jira issue property 활성화 (Jira 표준 REST + Basic auth) */
159645
159646
  async activateRiskPanel(issueKey) {
159646
159647
  const appId = "306e01a8-5530-427d-b93a-f91f4898ff16";
159647
159648
  const configId = "2a1d023a-d4a5-42c7-91e2-51a05b9eb9be";
@@ -159660,11 +159661,23 @@ var RiskClient = class {
159660
159661
  );
159661
159662
  return resp.ok;
159662
159663
  }
159663
- async setRiskValue(issueKey, riskConfig, classifierName, optionName) {
159664
- const classifier = riskConfig.classifiers[classifierName];
159665
- if (!classifier) throw new Error(`Unknown classifier: ${classifierName}`);
159666
- const optionId = classifier.options[optionName];
159667
- if (!optionId) throw new Error(`Unknown option: ${optionName} for ${classifierName}`);
159664
+ /**
159665
+ * Legacy 0.1.20 호환 — classifier/option name을 받아 lookup 후 setRawPayload 호출.
159666
+ * 0.1.21+ 핸들러는 planSetRisk 결과를 setRiskValueRaw로 직접 보내므로 이 메서드는
159667
+ * 그레이스존 유지용. 그러나 RISK_DEFAULTS 정의를 사용하지 않으므로 항상 undefined.
159668
+ */
159669
+ async setRiskValue(_issueKey, _riskConfig, _classifierName, _optionName) {
159670
+ return {
159671
+ ok: false,
159672
+ status: 0,
159673
+ error: "risk_set_values\uC740 0.1.21\uC5D0\uC11C setRiskValueRaw\uB85C \uC774\uC804\uB418\uC5C8\uC2B5\uB2C8\uB2E4. planSetRisk() \uC0AC\uC6A9 \uAD8C\uC7A5."
159674
+ };
159675
+ }
159676
+ /**
159677
+ * 신규 0.1.21 메서드 — payload가 완전 set된 상태로 RMP webtrigger 호출.
159678
+ * endpoint+token은 환경변수에서 받음 (회사 사이트마다 다름).
159679
+ */
159680
+ async setRiskValueRaw(issueKey, payload) {
159668
159681
  if (!this.riskAuthToken) {
159669
159682
  return {
159670
159683
  ok: false,
@@ -159672,24 +159685,27 @@ var RiskClient = class {
159672
159685
  error: "RISK_PLUGIN_AUTH_TOKEN not configured (x-trigger-authentication header would be empty)"
159673
159686
  };
159674
159687
  }
159675
- const payload = {
159688
+ const endpoint = process.env.RISK_PLUGIN_ENDPOINT;
159689
+ if (!endpoint) {
159690
+ return {
159691
+ ok: false,
159692
+ status: 0,
159693
+ error: "RISK_PLUGIN_ENDPOINT not configured. \uD68C\uC0AC DYN \uC0AC\uC774\uD2B8\uC758 RMP webtrigger URL\uC744 environment \uBCC0\uC218\uB85C \uC785\uB825\uD558\uC138\uC694."
159694
+ };
159695
+ }
159696
+ const fullPayload = {
159697
+ ...payload,
159676
159698
  issueKey,
159677
- author: riskConfig.author,
159678
- riskModelKey: riskConfig.riskModelKey,
159679
- riskSetId: riskConfig.riskSetId,
159680
- classifierValuePayload: {
159681
- classifierId: classifier.id,
159682
- optionId
159683
- }
159699
+ author: payload.author ?? ""
159684
159700
  };
159685
159701
  try {
159686
- const resp = await fetch(riskConfig.endpoint, {
159702
+ const resp = await fetch(endpoint, {
159687
159703
  method: "POST",
159688
159704
  headers: {
159689
159705
  "x-trigger-authentication": this.riskAuthToken,
159690
159706
  "Content-Type": "application/json"
159691
159707
  },
159692
- body: JSON.stringify(payload)
159708
+ body: JSON.stringify(fullPayload)
159693
159709
  });
159694
159710
  const body = await resp.text();
159695
159711
  return {
@@ -160081,57 +160097,244 @@ var registerConfluenceTools = (ctx2) => {
160081
160097
  });
160082
160098
  };
160083
160099
 
160084
- // src/config/project-config.ts
160085
- var RISK_DEFAULTS = {
160086
- endpoint: "https://306e01a8-5530-427d-b93a-f91f4898ff16.hello.atlassian-dev.net/x1/p3_h8rdLbpp1-9mjDOXQiqJ4jL4",
160087
- author: "712020:baa962da-2822-4cc7-9659-2d780f703b3e",
160088
- risk_model_key: "risk-model-9dc1c741-0858-4a96-65d6-5feaf052da71",
160089
- risk_sets: {
160090
- initial: "2f43f135-d043-84bd-4222-46ba4a309738",
160091
- current: "f957cae9-a34c-30d7-557b-f7f29a414ba5"
160092
- },
160093
- classifiers: {
160094
- severity: {
160095
- id: "120dbe93-8a5c-89d9-398c-6fdefc8fa072",
160096
- options: {
160097
- negligible: "0750b204-3be7-14b2-34c9-5100846e99db",
160098
- minor: "70ccea9f-a585-d248-dbd0-e6eefcc683e8",
160099
- serious: "a8b765c0-2e55-f6c4-25ff-1a4513c1cfd7",
160100
- critical: "587d166e-890a-5123-e179-15bafbd617e1",
160101
- catastrophic: "981fbfd5-604d-f9b9-6765-3405a165f401"
160100
+ // src/config/risk-models.ts
160101
+ var RISK_MODELS = {
160102
+ p1_p2_iso14971: {
160103
+ label: "P1-P2 ISO14971 Hazard analysis",
160104
+ modelKey: "risk-model-9dc1c741-0858-4a96-65d6-5feaf052da71",
160105
+ riskSets: {
160106
+ initial: "2f43f135-d043-84bd-4222-46ba4a309738",
160107
+ current: "f957cae9-a34c-30d7-557b-f7f29a414ba5"
160108
+ },
160109
+ classifiers: {
160110
+ severity: {
160111
+ id: "120dbe93-8a5c-89d9-398c-6fdefc8fa072",
160112
+ options: {
160113
+ negligible: "0750b204-3be7-14b2-34c9-5100846e99db",
160114
+ minor: "70ccea9f-a585-d248-dbd0-e6eefcc683e8",
160115
+ serious: "a8b765c0-2e55-f6c4-25ff-1a4513c1cfd7",
160116
+ critical: "587d166e-890a-5123-e179-15bafbd617e1",
160117
+ catastrophic: "981fbfd5-604d-f9b9-6765-3405a165f401"
160118
+ }
160119
+ },
160120
+ p1: {
160121
+ id: "c2c0af5d-74de-e463-ad13-ba80da1b6afd",
160122
+ options: {
160123
+ improvable: "3ac4a3b1-55ef-de93-3452-61c572a905a3",
160124
+ remote: "2d173a68-c28f-d589-1300-bc127f962810",
160125
+ occasional: "039596ac-784e-f0ac-50cf-d4d2cc96b096",
160126
+ probable: "702cb81f-3bc6-d150-5cc3-daa04d3f0404",
160127
+ frequent: "09fb301a-4448-d8aa-afd2-76d371f501f3"
160128
+ }
160129
+ },
160130
+ p2: {
160131
+ id: "cdada7bb-cb4a-3b00-260b-5e5227e85a74",
160132
+ options: {
160133
+ improvable: "7e044b34-25c4-ffae-5237-48eebb190358",
160134
+ remote: "58257cb8-59e2-5ec7-dd1d-9e966fb13589",
160135
+ occasional: "6cf5ebce-3b07-69b1-b093-9e0ef9def95c",
160136
+ probable: "bbcbfdd1-0c02-6105-e8e4-f60ae6cacb5c",
160137
+ frequent: "e35de539-93d5-f854-c6ed-50067ad8599b"
160138
+ }
160102
160139
  }
160140
+ }
160141
+ },
160142
+ rpn: {
160143
+ label: "Risk Score Calculator",
160144
+ modelKey: "risk-model-c3edfff2-bea1-eb85-1f53-1007c429357a",
160145
+ riskSets: {
160146
+ initial: "f0f50906-1516-0826-8e82-009813ce7ff7",
160147
+ current: "069094fa-c4b1-28a1-d330-464f78b4ecb5",
160148
+ final: "d972671a-06c1-cf5d-e73b-20779079b509"
160103
160149
  },
160104
- p1: {
160105
- id: "c2c0af5d-74de-e463-ad13-ba80da1b6afd",
160106
- options: {
160107
- improvable: "3ac4a3b1-55ef-de93-3452-61c572a905a3",
160108
- remote: "2d173a68-c28f-d589-1300-bc127f962810",
160109
- occasional: "039596ac-784e-f0ac-50cf-d4d2cc96b096",
160110
- probable: "702cb81f-3bc6-d150-5cc3-daa04d3f0404",
160111
- frequent: "09fb301a-4448-d8aa-afd2-76d371f501f3"
160150
+ classifiers: {
160151
+ severity: {
160152
+ id: "fec8bdcd-0c55-4ab3-87e3-cfd7176aefd5",
160153
+ options: {
160154
+ negligible: "a87a6249-03e4-5419-d50e-0818d824e8cb",
160155
+ minor: "f0f50906-1516-0826-8e82-009813ce7ff7",
160156
+ serious: "15f87aeb-28ec-98ea-7310-4ad04b294127",
160157
+ critical: "bf9cf219-e342-12ef-4305-2963f930138d",
160158
+ catastrophic: "e04cdc89-ffae-6001-4c06-d14b9fea0eee"
160159
+ }
160160
+ },
160161
+ occurrence: {
160162
+ id: "dc6e3abf-8cfa-b48f-5693-0f51a2362c8f",
160163
+ options: {
160164
+ improvable: "421dfccd-e545-2ec1-6d76-74913955cb63",
160165
+ remote: "8933daea-a933-3ea0-59c1-eb8b7aa0120e",
160166
+ occasional: "96861e6d-1b59-6616-7dda-0e6f413f4f65",
160167
+ probable: "40cd4688-3f91-3781-40c1-5ce65f7cb272",
160168
+ frequent: "7d1c1103-238d-a104-28d7-ba312dd4e394"
160169
+ }
160170
+ },
160171
+ detectability: {
160172
+ id: "2c266e79-f190-1234-95d1-201f72c98acc",
160173
+ options: {
160174
+ improvable: "f57c4581-c957-a270-65e6-1d6930b93a7f",
160175
+ remote: "f7fdea4b-5378-8c45-854d-bcd8ba3bee12",
160176
+ occasional: "45fb0720-f204-acf9-33dd-c92ba27910fa",
160177
+ probable: "c1a7a596-6fc4-db5d-1da3-f2b13d64da8c",
160178
+ frequent: "67ea818d-8ed7-a1af-57ff-6ba68a24c4b4"
160179
+ }
160112
160180
  }
160181
+ }
160182
+ },
160183
+ cia: {
160184
+ label: "Cyber Sec Risk Model",
160185
+ modelKey: "risk-model-ciaRiskModel",
160186
+ riskSets: {
160187
+ initial: "68f0e9de-26c6-7ad8-5a72-a688ad770b24",
160188
+ current: "b43d25e0-0ab5-3605-cd2d-3f54f904d26c"
160189
+ },
160190
+ ciaWrapper: {
160191
+ id: "54d9d60c-7c53-2454-a48d-3c464bad23a9",
160192
+ riskModelKey: "risk-model-overallCIAImpact"
160113
160193
  },
160114
- p2: {
160115
- id: "cdada7bb-cb4a-3b00-260b-5e5227e85a74",
160116
- options: {
160117
- improvable: "7e044b34-25c4-ffae-5237-48eebb190358",
160118
- remote: "58257cb8-59e2-5ec7-dd1d-9e966fb13589",
160119
- occasional: "6cf5ebce-3b07-69b1-b093-9e0ef9def95c",
160120
- probable: "bbcbfdd1-0c02-6105-e8e4-f60ae6cacb5c",
160121
- frequent: "e35de539-93d5-f854-c6ed-50067ad8599b"
160194
+ classifiers: {
160195
+ confidentiality: {
160196
+ id: "193ceea6-d531-808b-bb83-d16eb233d73f",
160197
+ options: {
160198
+ negligible: "9dc59ace-7484-108c-77fc-383c85a54052",
160199
+ low: "c1802cd2-a655-3ed9-8138-376297ab39ab",
160200
+ moderate: "d96e2893-515d-29da-d168-c134fb055fa1",
160201
+ high: "dc7f3d58-a8cd-7729-5e0a-37cedfe7996f",
160202
+ severe: "39fe9a59-c8b9-d0b5-a273-fbfcc6efdb6c"
160203
+ }
160204
+ },
160205
+ availability: {
160206
+ id: "1121ee15-e019-dbd7-64f2-47e7af76e789",
160207
+ options: {
160208
+ negligible: "863fd4fd-d939-4e73-fdc9-b27489fcbbb4",
160209
+ low: "cbb40d71-238c-97d7-02cd-4b905929eedb",
160210
+ moderate: "4fbc0ef0-1378-3b2c-7401-75ce94d9443b",
160211
+ high: "33a53544-c011-c30d-dc66-d5f9e0d0bb88",
160212
+ severe: "d9f4a792-0822-c0b0-b463-4c2887ce4d01"
160213
+ }
160214
+ },
160215
+ integrity: {
160216
+ id: "639c61e6-9d11-9bb3-04e7-c1c3512c7e33",
160217
+ options: {
160218
+ negligible: "f35284c1-52a2-edce-e60b-69b97703b891",
160219
+ low: "af67cc1b-9e25-76ce-baf3-d2e8247af569",
160220
+ moderate: "a35a0d14-e8d4-b3c8-35fc-a1f81d2730c7",
160221
+ high: "e535be53-21a8-0f8d-3c78-db4c3d7ebde1",
160222
+ severe: "7a45c4d7-d926-5d01-0034-c9ef929117b3"
160223
+ }
160224
+ },
160225
+ likelihood: {
160226
+ id: "ff7ed5a9-53fe-7e29-f564-b8f3b96bdd5b",
160227
+ options: {
160228
+ rare: "5b3f2233-4603-f34a-9e06-1473e4e1da02",
160229
+ unlikely: "7af594cb-4a87-73c1-2d0a-924124c5f247",
160230
+ possibly: "ed8a2a21-ec63-5e9b-d1d3-60d39ad4ec91",
160231
+ likely: "afe83dfb-fc00-7d3c-9b65-ed0ba6607b96",
160232
+ almost_certain: "86def84c-70c4-15c2-7b7b-f084a9aaf19f"
160233
+ }
160122
160234
  }
160123
160235
  }
160124
160236
  }
160125
160237
  };
160126
- function getRiskConfig(_projectKey) {
160127
- return RISK_DEFAULTS;
160238
+
160239
+ // src/config/project-config.ts
160240
+ function planSetRisk(model, riskSet, classifierOptions, issueKey) {
160241
+ const m3 = RISK_MODELS[model];
160242
+ if (!m3) return { items: [], skipped: [{ classifier: "*", optionName: model }] };
160243
+ const setId = m3.riskSets[riskSet];
160244
+ if (!setId) return { items: [], skipped: [{ classifier: "*", optionName: `riskSet.${riskSet}` }] };
160245
+ const author = process.env.JIRA_EMAIL;
160246
+ if (!author) return { items: [], skipped: [{ classifier: "*", optionName: "JIRA_EMAIL missing" }] };
160247
+ const items = [];
160248
+ const skipped = [];
160249
+ for (const [classifierName, optionName] of Object.entries(classifierOptions)) {
160250
+ if (!optionName) continue;
160251
+ const cls = m3.classifiers[classifierName];
160252
+ if (!cls) {
160253
+ skipped.push({ classifier: classifierName, optionName });
160254
+ continue;
160255
+ }
160256
+ const optionId = cls.options[optionName];
160257
+ if (!optionId) {
160258
+ skipped.push({ classifier: classifierName, optionName });
160259
+ continue;
160260
+ }
160261
+ items.push({
160262
+ model,
160263
+ riskSet,
160264
+ classifierName,
160265
+ optionName,
160266
+ payload: {
160267
+ issueKey,
160268
+ author,
160269
+ riskModelKey: m3.modelKey,
160270
+ riskSetId: setId,
160271
+ classifierValuePayload: { classifierId: cls.id, optionId }
160272
+ }
160273
+ });
160274
+ }
160275
+ return {
160276
+ items: items.map((i2) => ({ payload: i2.payload })),
160277
+ skipped
160278
+ };
160128
160279
  }
160129
160280
 
160130
160281
  // src/tools/risk.ts
160131
- function formatRiskResult(name, value, r2) {
160282
+ var SEVERITY_P1P2 = external_exports.enum(["negligible", "minor", "serious", "critical", "catastrophic"]);
160283
+ var PROB_P1P2 = external_exports.enum(["improvable", "remote", "occasional", "probable", "frequent"]);
160284
+ var SEVERITY_RPN = external_exports.enum(["negligible", "minor", "serious", "critical", "catastrophic"]);
160285
+ var PROB_RPN = external_exports.enum(["improvable", "remote", "occasional", "probable", "frequent"]);
160286
+ var CIA_LEVEL = external_exports.enum(["negligible", "low", "moderate", "high", "severe"]);
160287
+ var LIKELIHOOD = external_exports.enum(["rare", "unlikely", "possibly", "likely", "almost_certain"]);
160288
+ var RISK_SET = external_exports.enum(["initial", "current", "final"]).default("current");
160289
+ var p1P2Payload = external_exports.object({
160290
+ severity: SEVERITY_P1P2.optional(),
160291
+ p1: PROB_P1P2.optional(),
160292
+ p2: PROB_P1P2.optional()
160293
+ }).strict();
160294
+ var rpnPayload = external_exports.object({
160295
+ severity: SEVERITY_RPN.optional(),
160296
+ occurrence: PROB_RPN.optional(),
160297
+ detectability: PROB_RPN.optional()
160298
+ }).strict();
160299
+ var ciaPayload = external_exports.object({
160300
+ confidentiality: CIA_LEVEL.optional(),
160301
+ availability: CIA_LEVEL.optional(),
160302
+ integrity: CIA_LEVEL.optional(),
160303
+ likelihood: LIKELIHOOD.optional()
160304
+ }).strict();
160305
+ function classifierFromId(model, id) {
160306
+ const m3 = RISK_MODELS[model];
160307
+ for (const [name, cls] of Object.entries(m3.classifiers)) {
160308
+ if (cls.id === id) return name;
160309
+ }
160310
+ return id;
160311
+ }
160312
+ function formatSetDetail(line) {
160313
+ const r2 = line.result;
160132
160314
  const tag = r2.ok ? "OK" : "FAILED";
160133
160315
  const detail = r2.error ? `status=${r2.status}, error=${r2.error}` : `status=${r2.status}${r2.body ? `, body=${r2.body}` : ""}`;
160134
- return `${name}=${value}: ${tag} (${detail})`;
160316
+ return `${tag} [${line.model}] ${line.classifier}=${line.optionName} (${detail})`;
160317
+ }
160318
+ async function runSetPlans(riskStub, plans) {
160319
+ const out = { details: [] };
160320
+ for (const p of plans) {
160321
+ if (p.res.skipped.length > 0) {
160322
+ for (const sk of p.res.skipped) {
160323
+ out.skipped = out.skipped ?? [];
160324
+ out.skipped.push({ classifier: `${p.name}.${sk.classifier}`, reason: "option-not-mapped" });
160325
+ }
160326
+ }
160327
+ for (const { payload } of p.res.items) {
160328
+ const r2 = await riskStub.setRiskValueRaw(p.issueKey, payload);
160329
+ out.details.push({
160330
+ model: p.name,
160331
+ classifier: classifierFromId(p.model, payload.classifierValuePayload.classifierId),
160332
+ optionName: payload.classifierValuePayload.optionId,
160333
+ result: r2
160334
+ });
160335
+ }
160336
+ }
160337
+ return out;
160135
160338
  }
160136
160339
  var registerRiskTools = (ctx2) => {
160137
160340
  const { server: server2, risk: risk2, jira: jira2 } = ctx2;
@@ -160141,62 +160344,77 @@ var registerRiskTools = (ctx2) => {
160141
160344
  const ok = await risk2.activateRiskPanel(issue_key);
160142
160345
  return { content: [{ type: "text", text: ok ? `Risk panel activated on ${issue_key}` : `Failed to activate risk panel on ${issue_key}` }] };
160143
160346
  });
160144
- server2.tool("risk_set_values", "Set risk values (severity, p1, p2) on an issue via Risk Management Plugin", {
160145
- issue_key: external_exports.string().describe("Issue key"),
160146
- project_key: external_exports.string().describe("Project key (for config lookup)"),
160147
- severity: external_exports.enum(["negligible", "minor", "serious", "critical", "catastrophic"]).optional(),
160148
- p1: external_exports.enum(["improvable", "remote", "occasional", "probable", "frequent"]).optional(),
160149
- p2: external_exports.enum(["improvable", "remote", "occasional", "probable", "frequent"]).optional(),
160150
- risk_set: external_exports.enum(["initial", "current"]).default("current").describe("Which risk set to update")
160151
- }, async ({ issue_key, project_key, severity, p1, p2, risk_set }) => {
160152
- const riskConfig = getRiskConfig(project_key);
160153
- if (!riskConfig) return { content: [{ type: "text", text: `No risk management config for project ${project_key}. (\uD604\uC7AC \uC774 \uC11C\uBC84\uB294 \uB2E8\uC77C Risk \uC124\uC815\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4 \u2014 PROJECT_CONFIG_PATH \uB294 \uBBF8\uAD6C\uD604 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.)` }] };
160154
- const riskSetId = riskConfig.risk_sets[risk_set];
160155
- const configForSet = {
160156
- endpoint: riskConfig.endpoint,
160157
- author: riskConfig.author,
160158
- riskModelKey: riskConfig.risk_model_key,
160159
- riskSetId,
160160
- classifiers: riskConfig.classifiers
160161
- };
160162
- const results = [];
160163
- for (const [name, value] of [["severity", severity], ["p1", p1], ["p2", p2]]) {
160164
- if (value) {
160165
- const r2 = await risk2.setRiskValue(issue_key, configForSet, name, value);
160166
- results.push(formatRiskResult(name, value, r2));
160347
+ server2.tool(
160348
+ "risk_set_values",
160349
+ "Set risk values on an issue via RMP webtrigger. P1-P2 / RPN / CIA \uB3D9\uC2DC \uC785\uB825 \uAC00\uB2A5. \uC635\uC158\uC740 name \uBB38\uC790\uC5F4(UUID \uC790\uB3D9 \uB9E4\uD551). lookup \uC2E4\uD328 \uD56D\uBAA9\uC740 SKIP.",
160350
+ {
160351
+ issue_key: external_exports.string().describe("Issue key"),
160352
+ project_key: external_exports.string().describe("Project key (for context)"),
160353
+ risk_set: RISK_SET,
160354
+ p1_p2_iso14971: p1P2Payload.optional(),
160355
+ rpn: rpnPayload.optional(),
160356
+ cia: ciaPayload.optional()
160357
+ },
160358
+ async ({ issue_key, project_key, risk_set, p1_p2_iso14971, rpn, cia }) => {
160359
+ const tasks = [
160360
+ { model: "p1_p2_iso14971", name: "P1-P2", res: planSetRisk("p1_p2_iso14971", risk_set, p1_p2_iso14971 ?? {}, issue_key), issueKey: issue_key },
160361
+ { model: "rpn", name: "RPN", res: planSetRisk("rpn", risk_set, rpn ?? {}, issue_key), issueKey: issue_key },
160362
+ { model: "cia", name: "CIA", res: planSetRisk("cia", risk_set, cia ?? {}, issue_key), issueKey: issue_key }
160363
+ ];
160364
+ const out = await runSetPlans(risk2, tasks);
160365
+ const lines = [`Risk values for ${issue_key} (${risk_set} set):`];
160366
+ if (out.details.length === 0 && (out.skipped ?? []).length === 0) {
160367
+ lines.push(`(no risk values specified for ${project_key})`);
160167
160368
  }
160369
+ for (const d2 of out.details) lines.push(formatSetDetail(d2));
160370
+ for (const s2 of out.skipped ?? []) lines.push(`SKIPPED ${s2.classifier} (${s2.reason})`);
160371
+ return { content: [{ type: "text", text: lines.join("\n") }] };
160168
160372
  }
160169
- return { content: [{ type: "text", text: `Risk values for ${issue_key} (${risk_set} set):
160170
- ${results.join("\n")}` }] };
160171
- });
160172
- server2.tool("risk_get_config", "Get risk management configuration for a project", {
160173
- project_key: external_exports.string().describe("Project key")
160174
- }, async ({ project_key }) => {
160175
- const config2 = getRiskConfig(project_key);
160176
- if (!config2) return { content: [{ type: "text", text: `No risk config for ${project_key}` }] };
160177
- return { content: [{ type: "text", text: JSON.stringify({
160178
- risk_model_key: config2.risk_model_key,
160179
- risk_sets: config2.risk_sets,
160180
- classifiers: Object.fromEntries(Object.entries(config2.classifiers).map(([k, v2]) => [k, { id: v2.id, options: Object.keys(v2.options) }])),
160181
- // 주의: 이 값은 하드코딩된 RISK_DEFAULTS 출력이지, project_key 에 해당하는
160182
- // 실제 Risk 설정 조회가 아니다. set_values 실패 endpoint/token 문제일 있음.
160183
- diagnostic_note: "\uC774 \uC751\uB2F5\uC740 \uD558\uB4DC\uCF54\uB529\uB41C \uB2E8\uC77C \uC124\uC815 \uCD9C\uB825\uC784. \uC2E4\uC81C endpoint/token \uACFC \uC77C\uCE58\uD558\uB294\uC9C0 \uBCC4\uB3C4 \uD655\uC778 \uD544\uC694."
160184
- }, null, 2) }] };
160185
- });
160186
- server2.tool("risk_create_hazards", "Batch create Hazard tickets with Risk panel activation, risk values, and Risk Source links. Migrated from rmr_create_hazards.py.", {
160187
- project_key: external_exports.string().describe("Project key"),
160188
- rmr_key: external_exports.string().describe("RMR Document ticket key"),
160373
+ );
160374
+ server2.tool(
160375
+ "risk_get_config",
160376
+ "Get RMP risk model configuration for a project. Returns 3 model identifiers, classifier/option UUID \uD480, \uD658\uACBD \uC548\uB0B4.",
160377
+ {
160378
+ project_key: external_exports.string().describe("Project key")
160379
+ },
160380
+ async ({ project_key }) => {
160381
+ const endpoint = process.env.RISK_PLUGIN_ENDPOINT ?? null;
160382
+ const tokenPresent = !!process.env.RISK_PLUGIN_AUTH_TOKEN;
160383
+ return { content: [{ type: "text", text: JSON.stringify({
160384
+ endpoint,
160385
+ token_present: tokenPresent,
160386
+ diagnostic_note: "token\uC774 \uC5C6\uAC70\uB098 endpoint\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC73C\uBA74 setRiskValue \uD638\uCD9C\uC774 \uC2E4\uD328\uD569\uB2C8\uB2E4. /plugin configure \uB85C RISK_PLUGIN_ENDPOINT / RISK_PLUGIN_AUTH_TOKEN \uC744 \uC785\uB825.",
160387
+ models: Object.fromEntries(
160388
+ Object.keys(RISK_MODELS).map((k) => {
160389
+ const m3 = RISK_MODELS[k];
160390
+ return [k, {
160391
+ label: m3.label,
160392
+ modelKey: m3.modelKey,
160393
+ riskSets: m3.riskSets,
160394
+ classifiers: Object.fromEntries(
160395
+ Object.entries(m3.classifiers).map(([cn, c]) => [cn, { id: c.id, options: Object.keys(c.options) }])
160396
+ ),
160397
+ ...m3.ciaWrapper ? { ciaWrapper: m3.ciaWrapper } : {}
160398
+ }];
160399
+ })
160400
+ ),
160401
+ project: project_key
160402
+ }, null, 2) }] };
160403
+ }
160404
+ );
160405
+ server2.tool("risk_create_hazards", "Batch create Hazard tickets with Risk Source links and (\uC120\uD0DD\uC801) 3 \uBAA8\uB378 risk values \uC790\uB3D9 \uC14B\uD305.", {
160406
+ project_key: external_exports.string(),
160407
+ rmr_key: external_exports.string(),
160189
160408
  hazards: external_exports.array(external_exports.object({
160190
160409
  summary: external_exports.string(),
160191
160410
  description: external_exports.string(),
160192
160411
  harm: external_exports.string(),
160193
- severity: external_exports.enum(["negligible", "minor", "serious", "critical", "catastrophic"]),
160194
- p1: external_exports.enum(["improvable", "remote", "occasional", "probable", "frequent"]),
160195
- p2: external_exports.enum(["improvable", "remote", "occasional", "probable", "frequent"]),
160196
- source_keys: external_exports.array(external_exports.string()).describe("Related IU/SyRS ticket keys")
160197
- })).describe("Array of hazard definitions")
160412
+ source_keys: external_exports.array(external_exports.string()),
160413
+ p1_p2_iso14971: p1P2Payload.optional(),
160414
+ rpn: rpnPayload.optional(),
160415
+ cia: ciaPayload.optional()
160416
+ }).strict())
160198
160417
  }, async ({ project_key, rmr_key, hazards }) => {
160199
- const riskConfig = getRiskConfig(project_key);
160200
160418
  const { textToAdf: textToAdf2 } = await Promise.resolve().then(() => (init_jira_client(), jira_client_exports));
160201
160419
  const results = [];
160202
160420
  const errors = [];
@@ -160215,50 +160433,45 @@ ${results.join("\n")}` }] };
160215
160433
  continue;
160216
160434
  }
160217
160435
  await risk2.activateRiskPanel(issueKey);
160218
- let riskOk = true;
160219
- const riskDetails = [];
160220
- if (riskConfig) {
160221
- for (const setName of ["initial", "current"]) {
160222
- const setConfig = {
160223
- endpoint: riskConfig.endpoint,
160224
- author: riskConfig.author,
160225
- riskModelKey: riskConfig.risk_model_key,
160226
- riskSetId: riskConfig.risk_sets[setName],
160227
- classifiers: riskConfig.classifiers
160228
- };
160229
- for (const [name, value] of [["severity", h2.severity], ["p1", h2.p1], ["p2", h2.p2]]) {
160230
- const r2 = await risk2.setRiskValue(issueKey, setConfig, name, value);
160231
- if (!r2.ok) riskOk = false;
160232
- riskDetails.push(`${setName}.${name}=${value}: ${r2.ok ? "OK" : "FAILED"} (${r2.error ?? `status=${r2.status}${r2.body ? `, body=${r2.body}` : ""}`})`);
160233
- }
160234
- }
160235
- } else {
160236
- riskOk = false;
160237
- riskDetails.push("no risk config for project");
160238
- }
160436
+ const tasks = [
160437
+ { model: "p1_p2_iso14971", name: "P1-P2", res: planSetRisk("p1_p2_iso14971", "initial", h2.p1_p2_iso14971 ?? {}, issueKey), issueKey },
160438
+ { model: "rpn", name: "RPN", res: planSetRisk("rpn", "initial", h2.rpn ?? {}, issueKey), issueKey },
160439
+ { model: "cia", name: "CIA", res: planSetRisk("cia", "initial", h2.cia ?? {}, issueKey), issueKey }
160440
+ ];
160441
+ const out = await runSetPlans(risk2, tasks);
160442
+ const riskOk = out.details.every((d2) => d2.result.ok);
160443
+ const riskSkipped = out.skipped ?? [];
160239
160444
  for (const srcKey of h2.source_keys) {
160240
160445
  await jira2.createLink("Risk Source", issueKey, srcKey);
160241
160446
  }
160242
160447
  await jira2.createLink("Relates", issueKey, rmr_key);
160243
- results.push({ key: issueKey, summary: h2.summary, status: "created", risk_values: { ok: riskOk, details: riskDetails } });
160448
+ results.push({
160449
+ key: issueKey,
160450
+ summary: h2.summary,
160451
+ status: "created",
160452
+ risk_values: {
160453
+ ok: riskOk,
160454
+ details: out.details.map(formatSetDetail),
160455
+ skipped: riskSkipped
160456
+ }
160457
+ });
160244
160458
  } catch (e2) {
160245
160459
  errors.push(`${h2.summary}: ${e2.message}`);
160246
160460
  }
160247
160461
  }
160248
160462
  return { content: [{ type: "text", text: JSON.stringify({ created: results.length, failed: errors.length, results, errors }, null, 2) }] };
160249
160463
  });
160250
- server2.tool("risk_create_mitigation", "Create mitigation Requirements, link to Hazards, and update Hazard Current P2. Migrated from srs_create_requirements.py.", {
160251
- project_key: external_exports.string().describe("Project key"),
160252
- srs_key: external_exports.string().describe("SRS Document ticket key"),
160464
+ server2.tool("risk_create_mitigation", "Create mitigation Requirements, link to Hazards, and update Hazard Current P2 (P1-P2 \uBAA8\uB378).", {
160465
+ project_key: external_exports.string(),
160466
+ srs_key: external_exports.string(),
160253
160467
  requirements: external_exports.array(external_exports.object({
160254
160468
  summary: external_exports.string(),
160255
160469
  description: external_exports.string(),
160256
- mitigates: external_exports.array(external_exports.string()).describe("Hazard keys this requirement mitigates"),
160257
- implements: external_exports.array(external_exports.string()).describe("System Requirement keys this implements")
160470
+ mitigates: external_exports.array(external_exports.string()),
160471
+ implements: external_exports.array(external_exports.string())
160258
160472
  })),
160259
- hazard_risk_updates: external_exports.record(external_exports.object({ p2: external_exports.string() })).describe("Hazard key \u2192 new P2 value mapping")
160473
+ hazard_risk_updates: external_exports.record(external_exports.object({ p2: external_exports.string() }))
160260
160474
  }, async ({ project_key, srs_key, requirements, hazard_risk_updates }) => {
160261
- const riskConfig = getRiskConfig(project_key);
160262
160475
  const { textToAdf: textToAdf2 } = await Promise.resolve().then(() => (init_jira_client(), jira_client_exports));
160263
160476
  const results = [];
160264
160477
  const errors = [];
@@ -160288,20 +160501,15 @@ ${results.join("\n")}` }] };
160288
160501
  }
160289
160502
  }
160290
160503
  const riskUpdates = [];
160291
- if (riskConfig) {
160292
- const currentConfig = {
160293
- endpoint: riskConfig.endpoint,
160294
- author: riskConfig.author,
160295
- riskModelKey: riskConfig.risk_model_key,
160296
- riskSetId: riskConfig.risk_sets.current,
160297
- classifiers: riskConfig.classifiers
160298
- };
160299
- for (const [hazKey, update] of Object.entries(hazard_risk_updates)) {
160300
- const r2 = await risk2.setRiskValue(hazKey, currentConfig, "p2", update.p2);
160301
- riskUpdates.push(`${hazKey} P2\u2192${update.p2}: ${r2.ok ? "OK" : "FAILED"} (${r2.error ?? `status=${r2.status}${r2.body ? `, body=${r2.body}` : ""}`})`);
160504
+ for (const [hazKey, update] of Object.entries(hazard_risk_updates)) {
160505
+ const plan = planSetRisk("p1_p2_iso14971", "current", { p2: update.p2 }, hazKey);
160506
+ if (plan.skipped.length > 0) {
160507
+ riskUpdates.push(`SKIPPED ${hazKey} P2=${update.p2} (option-not-mapped)`);
160508
+ continue;
160302
160509
  }
160303
- } else {
160304
- riskUpdates.push("no risk config for project \u2014 P2 update skipped");
160510
+ const { payload } = plan.items[0];
160511
+ const r2 = await risk2.setRiskValueRaw(hazKey, payload);
160512
+ riskUpdates.push(formatSetDetail({ model: "P1-P2", classifier: "p2", optionName: update.p2, result: r2 }));
160305
160513
  }
160306
160514
  return { content: [{ type: "text", text: JSON.stringify({ requirements_created: results.length, results, risk_updates: riskUpdates, errors }, null, 2) }] };
160307
160515
  });
@@ -184760,7 +184968,7 @@ var xrayClientSecret = process.env.XRAY_CLIENT_SECRET || "";
184760
184968
  var xray = xrayClientId && xrayClientSecret ? new XrayClient(xrayClientId, xrayClientSecret, jiraUrl, jiraEmail, jiraToken) : null;
184761
184969
  var server = new McpServer({
184762
184970
  name: "jira-confluence",
184763
- version: "0.1.20"
184971
+ version: "0.1.21"
184764
184972
  });
184765
184973
  var ctx = { server, jira, confluence, risk, xray };
184766
184974
  registerJiraCrudTools(ctx);