@de-otio/epimethian-mcp 5.3.2 → 5.4.0

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/cli/index.js CHANGED
@@ -49324,7 +49324,7 @@ async function getPage(pageId, includeBody) {
49324
49324
  async function createPage(spaceId, title, body, parentId, clientLabel) {
49325
49325
  const cfg = await getConfig();
49326
49326
  const pageBody = stripAttributionFooter(toStorageFormat(body));
49327
- const epimethianTag = `Epimethian v${"5.3.2"}`;
49327
+ const epimethianTag = `Epimethian v${"5.4.0"}`;
49328
49328
  const versionMsg = cfg.attribution && clientLabel ? `Created by ${clientLabel} (via ${epimethianTag})` : `Created by ${epimethianTag}`;
49329
49329
  const payload = {
49330
49330
  title,
@@ -49349,7 +49349,7 @@ async function createPage(spaceId, title, body, parentId, clientLabel) {
49349
49349
  async function updatePage(pageId, opts) {
49350
49350
  const cfg = await getConfig();
49351
49351
  const newVersion = opts.version + 1;
49352
- const epimethianTag = `Epimethian v${"5.3.2"}`;
49352
+ const epimethianTag = `Epimethian v${"5.4.0"}`;
49353
49353
  const effectiveClient = cfg.attribution ? opts.clientLabel : void 0;
49354
49354
  let versionMessage;
49355
49355
  if (opts.versionMessage && effectiveClient)
@@ -49360,14 +49360,14 @@ async function updatePage(pageId, opts) {
49360
49360
  versionMessage = `Updated by ${effectiveClient} (via ${epimethianTag})`;
49361
49361
  else
49362
49362
  versionMessage = `Updated by ${epimethianTag}`;
49363
+ const pageBody = opts.body ? stripAttributionFooter(toStorageFormat(opts.body)) : void 0;
49363
49364
  const payload = {
49364
49365
  id: pageId,
49365
49366
  status: "current",
49366
49367
  title: opts.title,
49367
49368
  version: { number: newVersion, message: versionMessage }
49368
49369
  };
49369
- if (opts.body) {
49370
- const pageBody = stripAttributionFooter(toStorageFormat(opts.body));
49370
+ if (pageBody !== void 0) {
49371
49371
  payload.body = {
49372
49372
  representation: "storage",
49373
49373
  value: pageBody
@@ -49386,8 +49386,7 @@ async function updatePage(pageId, opts) {
49386
49386
  throw err;
49387
49387
  }
49388
49388
  const page = PageSchema.parse(raw);
49389
- if (opts.body) {
49390
- const pageBody = stripAttributionFooter(toStorageFormat(opts.body));
49389
+ if (pageBody !== void 0) {
49391
49390
  pageCache.set(pageId, newVersion, pageBody);
49392
49391
  }
49393
49392
  try {
@@ -49763,9 +49762,11 @@ function sanitizeCommentBody(body) {
49763
49762
  }
49764
49763
  return stripped;
49765
49764
  }
49766
- var HTML_TAG_RE = /<[a-z][a-z0-9]*(?::[a-z][a-z0-9-]*)?[\s>\/]/i;
49765
+ var HTML_TAG_RE = /<\/?[a-z][a-z0-9]*(?::[a-z][a-z0-9-]*)?[\s>\/]/i;
49766
+ var HTML_ENTITY_RE = /&(?:[a-zA-Z]+|#x?[0-9a-fA-F]+);/;
49767
49767
  function toStorageFormat(body) {
49768
- return HTML_TAG_RE.test(body) ? body : `<p>${body}</p>`;
49768
+ if (HTML_TAG_RE.test(body) || HTML_ENTITY_RE.test(body)) return body;
49769
+ return `<p>${body}</p>`;
49769
49770
  }
49770
49771
  function extractHeadings(storageHtml) {
49771
49772
  const headingRe = /<h([1-6])[^>]*>(.*?)<\/h\1>/gi;
@@ -49946,7 +49947,8 @@ function toMarkdownView(storageHtml) {
49946
49947
  return markdown;
49947
49948
  }
49948
49949
  function looksLikeMarkdown(body) {
49949
- if (/<ac:/i.test(body) || /<ri:/i.test(body)) {
49950
+ const withoutCodeBlocks = body.replace(/^(`{3,})[^\n]*\n[\s\S]*?^\1\s*$/gm, "");
49951
+ if (/<ac:/i.test(withoutCodeBlocks) || /<ri:/i.test(withoutCodeBlocks)) {
49950
49952
  return false;
49951
49953
  }
49952
49954
  const STRONG_MARKDOWN_SIGNALS = [
@@ -49973,7 +49975,11 @@ function looksLikeMarkdown(body) {
49973
49975
  /\*\*[^*]+\*\*/
49974
49976
  // inline bold **text**
49975
49977
  ];
49976
- return STRONG_MARKDOWN_SIGNALS.some((re) => re.test(body));
49978
+ if (STRONG_MARKDOWN_SIGNALS.some((re) => re.test(body))) {
49979
+ return true;
49980
+ }
49981
+ const trimmed = body.trimStart();
49982
+ return !/^<[a-zA-Z]/i.test(trimmed);
49977
49983
  }
49978
49984
  async function formatPage(page, optionsOrIncludeBody) {
49979
49985
  const options2 = typeof optionsOrIncludeBody === "boolean" ? { includeBody: optionsOrIncludeBody } : optionsOrIncludeBody;
@@ -56724,7 +56730,10 @@ function planUpdate(params) {
56724
56730
  } = params;
56725
56731
  if (replaceBody) {
56726
56732
  const newStorage2 = markdownToStorage(callerMarkdown, converterOptions);
56727
- return { newStorage: newStorage2, deletedTokens: [] };
56733
+ const { sidecar: sidecar2 } = tokeniseStorage(currentStorage);
56734
+ const droppedCount = Object.keys(sidecar2).length;
56735
+ const versionMessage2 = droppedCount > 0 ? `Wholesale rewrite (replace_body): dropped ${droppedCount} preserved element(s)` : void 0;
56736
+ return { newStorage: newStorage2, deletedTokens: [], versionMessage: versionMessage2 };
56728
56737
  }
56729
56738
  const { canonical, sidecar } = tokeniseStorage(currentStorage);
56730
56739
  const diff = diffTokens(canonical, callerMarkdown, sidecar);
@@ -56752,22 +56761,34 @@ function planUpdate(params) {
56752
56761
  }
56753
56762
 
56754
56763
  // src/server/converter/content-safety-guards.ts
56755
- var SHRINKAGE_GUARD_MIN_OLD_LEN = 500;
56764
+ var MACRO_LOSS_NOT_CONFIRMED = "MACRO_LOSS_NOT_CONFIRMED";
56765
+ var TABLE_LOSS_NOT_CONFIRMED = "TABLE_LOSS_NOT_CONFIRMED";
56766
+ var SHRINKAGE_GUARD_MIN_OLD_LEN = 200;
56756
56767
  var SHRINKAGE_GUARD_MAX_RATIO = 0.5;
56757
56768
  var STRUCTURE_GUARD_MIN_OLD_HEADINGS = 3;
56758
56769
  var STRUCTURE_GUARD_MAX_RATIO = 0.5;
56759
- var EMPTY_BODY_MIN_OLD_LEN = 500;
56760
- var EMPTY_BODY_MIN_TEXT_LEN = 100;
56770
+ var EMPTY_BODY_MIN_OLD_LEN = 100;
56771
+ var EMPTY_BODY_MIN_TEXT_LEN = 3;
56761
56772
  var HEADING_RE = /<h[1-6][^>]*>/gi;
56762
56773
  function countHeadings(storage) {
56763
56774
  const cleaned = storage.replace(/<ac:plain-text-body>[\s\S]*?<\/ac:plain-text-body>/g, "").replace(/<!--[\s\S]*?-->/g, "");
56764
56775
  return (cleaned.match(HEADING_RE) || []).length;
56765
56776
  }
56777
+ var STRUCTURED_MACRO_RE = /<ac:structured-macro[\s>]/gi;
56778
+ function countMacros(storage) {
56779
+ const cleaned = storage.replace(/<ac:plain-text-body>[\s\S]*?<\/ac:plain-text-body>/g, "").replace(/<!--[\s\S]*?-->/g, "");
56780
+ return (cleaned.match(STRUCTURED_MACRO_RE) || []).length;
56781
+ }
56782
+ var TABLE_RE = /<table[\s>]/gi;
56783
+ function countTables(storage) {
56784
+ const cleaned = storage.replace(/<ac:plain-text-body>[\s\S]*?<\/ac:plain-text-body>/g, "").replace(/<!--[\s\S]*?-->/g, "");
56785
+ return (cleaned.match(TABLE_RE) || []).length;
56786
+ }
56766
56787
  function extractTextContent(storage) {
56767
- return storage.replace(/<!--[\s\S]*?-->/g, "").replace(/<[^>]*>/g, "").replace(/&[a-zA-Z]+;/g, " ").replace(/&#x?[0-9a-fA-F]+;/g, " ").trim();
56788
+ return storage.replace(/<!--[\s\S]*?-->/g, "").replace(/<[^>]*>/g, "").replace(/&nbsp;/gi, " ").replace(/&[a-zA-Z]+;/g, "_").replace(/&#x?[0-9a-fA-F]+;/g, "_").trim();
56768
56789
  }
56769
56790
  function enforceContentSafetyGuards(input) {
56770
- const { oldStorage, newStorage, confirmShrinkage, confirmStructureLoss } = input;
56791
+ const { oldStorage, newStorage, confirmShrinkage, confirmStructureLoss, confirmDeletions } = input;
56771
56792
  const oldLen = oldStorage.length;
56772
56793
  const newLen = newStorage.length;
56773
56794
  if (oldLen > SHRINKAGE_GUARD_MIN_OLD_LEN && newLen < oldLen * SHRINKAGE_GUARD_MAX_RATIO && !confirmShrinkage) {
@@ -56792,6 +56813,22 @@ function enforceContentSafetyGuards(input) {
56792
56813
  EMPTY_BODY_REJECTED
56793
56814
  );
56794
56815
  }
56816
+ const oldMacros = countMacros(oldStorage);
56817
+ const newMacros = countMacros(newStorage);
56818
+ if (oldMacros > 0 && newMacros === 0 && !confirmShrinkage && !confirmDeletions) {
56819
+ throw new ConverterError(
56820
+ `All ${oldMacros} Confluence macro(s) would be removed from the page. This may indicate accidental content loss (e.g. a lossy markdown round-trip). Re-submit with confirm_shrinkage: true if this is intentional.`,
56821
+ MACRO_LOSS_NOT_CONFIRMED
56822
+ );
56823
+ }
56824
+ const oldTables = countTables(oldStorage);
56825
+ const newTables = countTables(newStorage);
56826
+ if (oldTables > 0 && newTables === 0 && !confirmStructureLoss && !confirmDeletions) {
56827
+ throw new ConverterError(
56828
+ `All ${oldTables} table(s) would be removed from the page. This may indicate accidental content loss. Re-submit with confirm_structure_loss: true if this is intentional.`,
56829
+ TABLE_LOSS_NOT_CONFIRMED
56830
+ );
56831
+ }
56795
56832
  }
56796
56833
 
56797
56834
  // src/server/converter/storage-to-md.ts
@@ -56822,8 +56859,12 @@ function storageToMarkdown(storage) {
56822
56859
  // src/server/mutation-log.ts
56823
56860
  var import_node_fs = require("node:fs");
56824
56861
  var import_node_path2 = require("node:path");
56862
+ var import_node_crypto2 = require("node:crypto");
56825
56863
  var MAX_LOG_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
56826
56864
  var MAX_ERROR_LEN = 200;
56865
+ function bodyHash(body) {
56866
+ return (0, import_node_crypto2.createHash)("sha256").update(body).digest("hex").slice(0, 16);
56867
+ }
56827
56868
  var logPath = null;
56828
56869
  var logFd = null;
56829
56870
  function sanitizeErrorMessage(err) {
@@ -56890,7 +56931,7 @@ function errorRecord(operation, pageId, err, extra) {
56890
56931
  var import_promises2 = require("node:fs/promises");
56891
56932
  var import_node_path3 = require("node:path");
56892
56933
  var import_node_os2 = require("node:os");
56893
- var import_node_crypto2 = require("node:crypto");
56934
+ var import_node_crypto3 = require("node:crypto");
56894
56935
  var import_node_child_process2 = require("node:child_process");
56895
56936
  var import_node_util = require("node:util");
56896
56937
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
@@ -56933,7 +56974,7 @@ async function writeCheckState(state) {
56933
56974
  const data = JSON.stringify(state, null, 2) + "\n";
56934
56975
  const tmpFile = (0, import_node_path3.join)(
56935
56976
  CONFIG_DIR2,
56936
- `.update-check.${(0, import_node_crypto2.randomBytes)(4).toString("hex")}.tmp`
56977
+ `.update-check.${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.tmp`
56937
56978
  );
56938
56979
  await (0, import_promises2.writeFile)(tmpFile, data, { mode: 384 });
56939
56980
  await (0, import_promises2.rename)(tmpFile, UPDATE_CHECK_FILE);
@@ -57207,11 +57248,16 @@ function registerTools(server, config3) {
57207
57248
  throw new Error("Combined body exceeds 2MB limit");
57208
57249
  }
57209
57250
  const newBody = position === "prepend" ? contentStorage + sep + currentStorage : currentStorage + sep + contentStorage;
57251
+ enforceContentSafetyGuards({
57252
+ oldStorage: currentStorage,
57253
+ newStorage: newBody
57254
+ });
57210
57255
  const { page, newVersion } = await updatePage(page_id, {
57211
57256
  title: currentPage.title,
57212
57257
  body: newBody,
57213
57258
  version: version2,
57214
57259
  versionMessage: opts.versionMessage,
57260
+ previousBody: currentStorage,
57215
57261
  clientLabel: getClientLabel(server)
57216
57262
  });
57217
57263
  return { page, newVersion, oldLen: currentStorage.length, newLen: newBody.length };
@@ -57260,7 +57306,9 @@ function registerTools(server, config3) {
57260
57306
  operation: "create_page",
57261
57307
  pageId: page.id,
57262
57308
  newVersion: page.version?.number ?? 1,
57263
- newBodyLen: finalBody.length
57309
+ newBodyLen: finalBody.length,
57310
+ newBodyHash: bodyHash(finalBody),
57311
+ clientLabel: getClientLabel(server)
57264
57312
  });
57265
57313
  return toolResult(await formatPage(page, false) + echo);
57266
57314
  } catch (err) {
@@ -57423,7 +57471,11 @@ ${truncated}`);
57423
57471
  oldStorage: currentStorage,
57424
57472
  newStorage: finalStorage,
57425
57473
  confirmShrinkage: confirm_shrinkage,
57426
- confirmStructureLoss: confirm_structure_loss
57474
+ confirmStructureLoss: confirm_structure_loss,
57475
+ // confirm_deletions and replace_body both indicate the caller
57476
+ // has acknowledged macro/element removal — bypass the macro
57477
+ // and table loss guards, but NOT the shrinkage/structure guards.
57478
+ confirmDeletions: confirm_deletions || replace_body
57427
57479
  });
57428
57480
  }
57429
57481
  const { page, newVersion } = await updatePage(page_id, {
@@ -57442,6 +57494,9 @@ ${truncated}`);
57442
57494
  newVersion,
57443
57495
  oldBodyLen: currentStorage.length,
57444
57496
  newBodyLen: finalStorage?.length ?? currentStorage.length,
57497
+ oldBodyHash: bodyHash(currentStorage),
57498
+ newBodyHash: finalStorage ? bodyHash(finalStorage) : void 0,
57499
+ clientLabel: getClientLabel(server),
57445
57500
  replaceBody: replace_body || void 0
57446
57501
  });
57447
57502
  const bodyReport = finalStorage !== void 0 ? `body: ${currentStorage.length}\u2192${finalStorage.length} chars` : `title only, body unchanged`;
@@ -57517,6 +57572,10 @@ ${truncated}`);
57517
57572
  `Section "${section}" not found. Use headings_only to see available sections.`
57518
57573
  );
57519
57574
  }
57575
+ enforceContentSafetyGuards({
57576
+ oldStorage: fullBody,
57577
+ newStorage: newFullBody
57578
+ });
57520
57579
  const { page: updated, newVersion } = await updatePage(page_id, {
57521
57580
  title: page.title,
57522
57581
  body: newFullBody,
@@ -57532,7 +57591,10 @@ ${truncated}`);
57532
57591
  oldVersion: version2,
57533
57592
  newVersion,
57534
57593
  oldBodyLen: fullBody.length,
57535
- newBodyLen: newFullBody.length
57594
+ newBodyLen: newFullBody.length,
57595
+ oldBodyHash: bodyHash(fullBody),
57596
+ newBodyHash: bodyHash(newFullBody),
57597
+ clientLabel: getClientLabel(server)
57536
57598
  });
57537
57599
  return toolResult(
57538
57600
  `Updated section "${section}" in: ${updated.title} (ID: ${updated.id}, version: ${newVersion})` + echo
@@ -57923,17 +57985,35 @@ ${truncated}`);
57923
57985
  const existingBody = current.body?.storage?.value ?? current.body?.value ?? "";
57924
57986
  const newBody = append ? `${existingBody}
57925
57987
  ${macro}` : macro;
57988
+ enforceContentSafetyGuards({
57989
+ oldStorage: existingBody,
57990
+ newStorage: newBody
57991
+ });
57926
57992
  const { page, newVersion } = await updatePage(page_id, {
57927
57993
  title: current.title,
57928
57994
  body: newBody,
57929
57995
  version: current.version?.number ?? 0,
57930
57996
  versionMessage: `Added diagram: ${filename}`,
57997
+ previousBody: existingBody,
57998
+ clientLabel: getClientLabel(server)
57999
+ });
58000
+ logMutation({
58001
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
58002
+ operation: "update_page",
58003
+ pageId: page_id,
58004
+ oldVersion: current.version?.number ?? 0,
58005
+ newVersion,
58006
+ oldBodyLen: existingBody.length,
58007
+ newBodyLen: newBody.length,
58008
+ oldBodyHash: bodyHash(existingBody),
58009
+ newBodyHash: bodyHash(newBody),
57931
58010
  clientLabel: getClientLabel(server)
57932
58011
  });
57933
58012
  return toolResult(
57934
58013
  `Diagram "${filename}" added to page ${page.title} (ID: ${page.id}, version: ${newVersion})` + echo
57935
58014
  );
57936
58015
  } catch (err) {
58016
+ logMutation(errorRecord("update_page", page_id, err));
57937
58017
  return toolError(err);
57938
58018
  }
57939
58019
  }
@@ -58448,6 +58528,7 @@ ${result.diff}${truncNote}` + echo
58448
58528
  body: historical.rawBody,
58449
58529
  version: current_version,
58450
58530
  versionMessage: version_message ?? `Revert to version ${target_version}`,
58531
+ previousBody: currentStorage,
58451
58532
  clientLabel: getClientLabel(server)
58452
58533
  });
58453
58534
  logMutation({
@@ -58457,7 +58538,10 @@ ${result.diff}${truncNote}` + echo
58457
58538
  oldVersion: current_version,
58458
58539
  newVersion,
58459
58540
  oldBodyLen: currentStorage.length,
58460
- newBodyLen: historical.rawBody.length
58541
+ newBodyLen: historical.rawBody.length,
58542
+ oldBodyHash: bodyHash(currentStorage),
58543
+ newBodyHash: bodyHash(historical.rawBody),
58544
+ clientLabel: getClientLabel(server)
58461
58545
  });
58462
58546
  return toolResult(
58463
58547
  `Reverted: ${page.title} (ID: ${page.id}, v${target_version}\u2192v${newVersion}, body: ${currentStorage.length}\u2192${historical.rawBody.length} chars)` + echo
@@ -58540,7 +58624,7 @@ ${lines.join("\n")}${echo2}`
58540
58624
  inputSchema: {}
58541
58625
  },
58542
58626
  async () => {
58543
- let text2 = `epimethian-mcp v${"5.3.2"}`;
58627
+ let text2 = `epimethian-mcp v${"5.4.0"}`;
58544
58628
  try {
58545
58629
  const pending = await getPendingUpdate();
58546
58630
  if (pending) {
@@ -58570,7 +58654,7 @@ ${pending.type === "major" ? "Major" : "Minor"} update available: v${pending.cur
58570
58654
  const pending = await getPendingUpdate();
58571
58655
  if (!pending) {
58572
58656
  return toolResult(
58573
- `epimethian-mcp v${"5.3.2"} is already up to date.`
58657
+ `epimethian-mcp v${"5.4.0"} is already up to date.`
58574
58658
  );
58575
58659
  }
58576
58660
  const output = await performUpgrade(pending.latest);
@@ -58598,12 +58682,12 @@ async function main() {
58598
58682
  const serverName = config3.profile ? `confluence-${config3.profile}` : "confluence";
58599
58683
  const server = new McpServer({
58600
58684
  name: serverName,
58601
- version: "5.3.2"
58685
+ version: "5.4.0"
58602
58686
  });
58603
58687
  registerTools(server, config3);
58604
58688
  const transport = new StdioServerTransport();
58605
58689
  await server.connect(transport);
58606
- checkForUpdates("5.3.2").catch(() => {
58690
+ checkForUpdates("5.4.0").catch(() => {
58607
58691
  });
58608
58692
  }
58609
58693