@neobiotechlabs/neobiotech-dev-agent 0.1.35 → 0.1.37
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +76 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
13
|
+
"version": "0.1.37"
|
|
14
14
|
},
|
|
15
15
|
"description": "의료기기 SW 규제 개발 자동화 (Jira·Confluence·Risk·CVSS·Xray·IEC 62304/62366·MDR·문서 렌더)",
|
|
16
16
|
"category": "medical-device",
|
package/dist/index.js
CHANGED
|
@@ -159735,6 +159735,31 @@ true\r
|
|
|
159735
159735
|
clearTimeout(timer);
|
|
159736
159736
|
}
|
|
159737
159737
|
}
|
|
159738
|
+
/**
|
|
159739
|
+
* 페이지 첨부 목록 조회. Confluence REST는 페이지 자체 attachments 도 expand 가능하지만
|
|
159740
|
+
* 명시적 list도 지원 — 여기선 GET child/attachment?limit=N.
|
|
159741
|
+
*/
|
|
159742
|
+
async listAttachments(pageId, limit = 100) {
|
|
159743
|
+
const resp = await this.request(
|
|
159744
|
+
`${this.baseUrl}/wiki/rest/api/content/${encodeURIComponent(pageId)}/child/attachment?limit=${limit}`,
|
|
159745
|
+
{ label: "listAttachments" }
|
|
159746
|
+
);
|
|
159747
|
+
const data = resp;
|
|
159748
|
+
const items = Array.isArray(data?.results) ? data.results : [];
|
|
159749
|
+
return items.map((it2) => ({ id: String(it2.id), title: String(it2.title ?? "") }));
|
|
159750
|
+
}
|
|
159751
|
+
/**
|
|
159752
|
+
* 첨부 삭제. POST .../child/attachment/{id}/remove 와 동일 — REST API.
|
|
159753
|
+
* 위 endpoint URL의 page_id는 첫 pageId(이 attachment가 속한 페이지)지만, GET 응답의 id 경로의
|
|
159754
|
+
* second segment가 attachment id. Confluence Cloud는 id로 직접 delete 가능:
|
|
159755
|
+
* DELETE /wiki/rest/api/content/{pageId}/child/attachment/{attachmentId}
|
|
159756
|
+
*/
|
|
159757
|
+
async deleteAttachment(pageId, attachmentId) {
|
|
159758
|
+
await this.request(
|
|
159759
|
+
`${this.baseUrl}/wiki/rest/api/content/${encodeURIComponent(pageId)}/child/attachment/${encodeURIComponent(attachmentId)}`,
|
|
159760
|
+
{ method: "DELETE", label: "deleteAttachment" }
|
|
159761
|
+
);
|
|
159762
|
+
}
|
|
159738
159763
|
};
|
|
159739
159764
|
function escapeMimeFilename2(name) {
|
|
159740
159765
|
return name.replace(/"/g, "%22").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
@@ -160094,7 +160119,8 @@ var registerJiraCrudTools = (ctx2) => {
|
|
|
160094
160119
|
// src/tools/confluence.ts
|
|
160095
160120
|
init_markdown();
|
|
160096
160121
|
import { readFileSync } from "fs";
|
|
160097
|
-
import { resolve as resolve2 } from "path";
|
|
160122
|
+
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
160123
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
160098
160124
|
|
|
160099
160125
|
// src/utils/plantuml-renderer.ts
|
|
160100
160126
|
import { spawn } from "child_process";
|
|
@@ -160197,10 +160223,12 @@ async function renderPlantUml(source, opts = {}) {
|
|
|
160197
160223
|
// src/tools/confluence.ts
|
|
160198
160224
|
function readPluginVersion() {
|
|
160199
160225
|
try {
|
|
160200
|
-
const
|
|
160201
|
-
|
|
160202
|
-
|
|
160203
|
-
return "unknown-
|
|
160226
|
+
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
160227
|
+
const pkgPath = resolve2(here, "package.json");
|
|
160228
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
160229
|
+
return pkg.version || "unknown-no-version";
|
|
160230
|
+
} catch (err) {
|
|
160231
|
+
return "unknown-read-failed: " + (err?.message ?? String(err));
|
|
160204
160232
|
}
|
|
160205
160233
|
}
|
|
160206
160234
|
var PLUGIN_VERSION = readPluginVersion();
|
|
@@ -160264,9 +160292,10 @@ var registerConfluenceTools = (ctx2) => {
|
|
|
160264
160292
|
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."),
|
|
160265
160293
|
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."),
|
|
160266
160294
|
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."),
|
|
160295
|
+
clean_stale_attachments: external_exports.boolean().optional().default(false).describe("update \uBAA8\uB4DC\uC77C \uB54C \uAC8C\uC7AC \uC9C1\uC804 \uD398\uC774\uC9C0\uC5D0\uC11C diagram-* prefix \uCCA8\uBD80 \uC77C\uAD04 \uC0AD\uC81C. \uB204\uC801 \uBC29\uC9C0\uC6A9. Default false (\uC548\uC804). \uCCAB \uAC8C\uC7AC(create)\uC5D4 \uD6A8\uACFC \uC5C6\uC74C."),
|
|
160267
160296
|
diagram_format: external_exports.enum(["png", "svg"]).optional().default("png").describe("Image format for rendered diagrams. Default png."),
|
|
160268
160297
|
diagram_dpi: external_exports.number().optional().default(96).describe("Rendering DPI (PNG only, ignored for SVG). Default 96.")
|
|
160269
|
-
}, async ({ space_key, title, markdown, markdown_file, parent_page_id, page_id, render_diagrams = true, diagram_format = "png", diagram_dpi = 96 }) => {
|
|
160298
|
+
}, async ({ space_key, title, markdown, markdown_file, parent_page_id, page_id, render_diagrams = true, clean_stale_attachments = false, diagram_format = "png", diagram_dpi = 96 }) => {
|
|
160270
160299
|
if (!markdown && !markdown_file) {
|
|
160271
160300
|
throw new Error("Either markdown or markdown_file must be provided");
|
|
160272
160301
|
}
|
|
@@ -160326,20 +160355,49 @@ var registerConfluenceTools = (ctx2) => {
|
|
|
160326
160355
|
currentPageId = result?.id;
|
|
160327
160356
|
currentVersion = result?.version?.number ?? 1;
|
|
160328
160357
|
}
|
|
160358
|
+
if (mode === "update" && clean_stale_attachments) {
|
|
160359
|
+
renderTrace.push("cleanup entry");
|
|
160360
|
+
try {
|
|
160361
|
+
const existing = await confluence2.listAttachments(currentPageId);
|
|
160362
|
+
const targets = existing.filter((a) => /^diagram-/.test(a.title));
|
|
160363
|
+
let deleted = 0;
|
|
160364
|
+
for (const att of targets) {
|
|
160365
|
+
try {
|
|
160366
|
+
await confluence2.deleteAttachment(currentPageId, att.id);
|
|
160367
|
+
deleted++;
|
|
160368
|
+
} catch (err) {
|
|
160369
|
+
renderTrace.push(`cleanup delete FAIL ${att.title}: ${err?.message ?? err}`);
|
|
160370
|
+
}
|
|
160371
|
+
}
|
|
160372
|
+
renderTrace.push(`cleanup done: deleted=${deleted}/${targets.length}`);
|
|
160373
|
+
} catch (err) {
|
|
160374
|
+
renderTrace.push(`cleanup FAIL: ${err?.message ?? err}`);
|
|
160375
|
+
}
|
|
160376
|
+
}
|
|
160329
160377
|
let finalStorage = storage;
|
|
160378
|
+
renderTrace.push(`round2 entry: renderedDiagrams.size=${renderedDiagrams.size}`);
|
|
160330
160379
|
if (render_diagrams && renderedDiagrams.size > 0) {
|
|
160380
|
+
let attIter = 0;
|
|
160381
|
+
const attachmentNames = /* @__PURE__ */ new Map();
|
|
160331
160382
|
for (const [id, diag] of renderedDiagrams) {
|
|
160332
|
-
|
|
160383
|
+
attIter++;
|
|
160384
|
+
const filename = `diagram-${id}-${Date.now()}.${diag.format}`;
|
|
160333
160385
|
try {
|
|
160334
160386
|
await confluence2.addAttachment(currentPageId, filename, diag.buffer, diag.contentType);
|
|
160387
|
+
attachmentNames.set(id, filename);
|
|
160388
|
+
renderTrace.push(`attach ${attIter}/${renderedDiagrams.size}: ${filename} OK`);
|
|
160335
160389
|
} catch (err) {
|
|
160390
|
+
renderTrace.push(`attach ${attIter}/${renderedDiagrams.size}: ${filename} FAIL \u2014 ${err?.message ?? err}`);
|
|
160336
160391
|
console.error(`[confluence_publish_doc] attachment upload failed for ${id}: ${err?.message ?? err}`);
|
|
160337
160392
|
renderedDiagrams.delete(id);
|
|
160338
160393
|
diagramStats.rendered--;
|
|
160339
160394
|
}
|
|
160340
160395
|
}
|
|
160341
|
-
|
|
160396
|
+
renderTrace.push(`round2 pre-replace: renderedDiagrams.size=${renderedDiagrams.size}`);
|
|
160397
|
+
finalStorage = replacePumlMarkers(storage, renderedDiagrams, pumlRegistry, attachmentNames);
|
|
160398
|
+
renderTrace.push(`round2 post-replace: finalStorage.length=${finalStorage.length}, contains-image=${finalStorage.includes("<ac:image")}`);
|
|
160342
160399
|
result = await confluence2.updatePage(currentPageId, title, finalStorage, currentVersion);
|
|
160400
|
+
renderTrace.push(`round2 update: v${result?.version?.number}, body_chars=${finalStorage.length}`);
|
|
160343
160401
|
versionsUsed = 2;
|
|
160344
160402
|
}
|
|
160345
160403
|
const pageIdOut = result?.id ?? currentPageId;
|
|
@@ -160371,10 +160429,10 @@ var registerConfluenceTools = (ctx2) => {
|
|
|
160371
160429
|
};
|
|
160372
160430
|
});
|
|
160373
160431
|
};
|
|
160374
|
-
function replacePumlMarkers(storage, rendered, pumlRegistry) {
|
|
160432
|
+
function replacePumlMarkers(storage, rendered, pumlRegistry, attachmentNames) {
|
|
160375
160433
|
let out = storage;
|
|
160376
160434
|
for (const [id, diag] of rendered) {
|
|
160377
|
-
const filename = `diagram-${id}.${diag.format}`;
|
|
160435
|
+
const filename = attachmentNames?.get(id) ?? `diagram-${id}.${diag.format}`;
|
|
160378
160436
|
const imageMacro = `<ac:image ac:alt="PlantUML Diagram ${id}"><ri:attachment ri:filename="${filename}"/></ac:image>`;
|
|
160379
160437
|
out = out.replace(
|
|
160380
160438
|
new RegExp(`<p data-puml-id="${id}"></p>`),
|
|
@@ -162114,7 +162172,7 @@ var registerInitTools = ({ server: server2 }) => {
|
|
|
162114
162172
|
|
|
162115
162173
|
// src/tools/doc-render.ts
|
|
162116
162174
|
import { readFileSync as readFileSync3 } from "fs";
|
|
162117
|
-
import { fileURLToPath as
|
|
162175
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
162118
162176
|
import path from "path";
|
|
162119
162177
|
|
|
162120
162178
|
// src/utils/doc-render.ts
|
|
@@ -162179,7 +162237,7 @@ var TEMPLATE_VERSION = "17d0c4e";
|
|
|
162179
162237
|
|
|
162180
162238
|
// src/tools/doc-render.ts
|
|
162181
162239
|
init_adf();
|
|
162182
|
-
var __dirname2 = path.dirname(
|
|
162240
|
+
var __dirname2 = path.dirname(fileURLToPath3(import.meta.url));
|
|
162183
162241
|
var TEMPLATE_DIR = path.resolve(__dirname2, "../vendor/dev-docs-templates/templates");
|
|
162184
162242
|
var registerDocRenderTools = (ctx2) => {
|
|
162185
162243
|
const { server: server2, jira: jira2 } = ctx2;
|
|
@@ -184326,13 +184384,13 @@ init_marked_esm();
|
|
|
184326
184384
|
|
|
184327
184385
|
// src/utils/pdf-fonts.ts
|
|
184328
184386
|
import { existsSync as existsSync3 } from "fs";
|
|
184329
|
-
import { join as join4, dirname as
|
|
184330
|
-
import { fileURLToPath as
|
|
184387
|
+
import { join as join4, dirname as dirname3 } from "path";
|
|
184388
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
184331
184389
|
var BUNDLED_FONT_FAMILY = "BodyFont";
|
|
184332
184390
|
var BUNDLED_FONT_FAMILY_BOLD = "BodyFont-Bold";
|
|
184333
184391
|
var DEFAULT_FAMILY = "BodyFont";
|
|
184334
184392
|
function bundledFontPaths() {
|
|
184335
|
-
const here = typeof __dirname !== "undefined" ? __dirname :
|
|
184393
|
+
const here = typeof __dirname !== "undefined" ? __dirname : dirname3(fileURLToPath4(import.meta.url));
|
|
184336
184394
|
const rootFromSrc = join4(here, "..", "..");
|
|
184337
184395
|
const candidates = [join4(here, "fonts"), join4(rootFromSrc, "dist", "fonts")];
|
|
184338
184396
|
const fontsDir = candidates.find(
|
|
@@ -185503,9 +185561,9 @@ var registerExpertTools = (ctx2) => {
|
|
|
185503
185561
|
|
|
185504
185562
|
// src/tools/review-criteria.ts
|
|
185505
185563
|
import { readFileSync as readFileSync5 } from "fs";
|
|
185506
|
-
import { fileURLToPath as
|
|
185564
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
185507
185565
|
import path2 from "path";
|
|
185508
|
-
var __dirname3 = path2.dirname(
|
|
185566
|
+
var __dirname3 = path2.dirname(fileURLToPath5(import.meta.url));
|
|
185509
185567
|
var CRITERIA_PATH = path2.resolve(__dirname3, "../data/review-criteria.md");
|
|
185510
185568
|
var registerReviewCriteriaTools = (ctx2) => {
|
|
185511
185569
|
ctx2.server.tool(
|
|
@@ -185552,7 +185610,7 @@ var xrayClientSecret = process.env.XRAY_CLIENT_SECRET || "";
|
|
|
185552
185610
|
var xray = xrayClientId && xrayClientSecret ? new XrayClient(xrayClientId, xrayClientSecret, jiraUrl, jiraEmail, jiraToken) : null;
|
|
185553
185611
|
var server = new McpServer({
|
|
185554
185612
|
name: "jira-confluence",
|
|
185555
|
-
version: "0.1.
|
|
185613
|
+
version: "0.1.37"
|
|
185556
185614
|
});
|
|
185557
185615
|
var ctx = { server, jira, confluence, risk, xray };
|
|
185558
185616
|
registerJiraCrudTools(ctx);
|