mrvn-cli 0.5.11 → 0.5.12

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.
@@ -15711,7 +15711,9 @@ function collectSprintSummaryData(store, sprintId) {
15711
15711
  workFocus: focusTag ? focusTag.slice(6) : void 0,
15712
15712
  aboutArtifact: about,
15713
15713
  jiraKey: doc.frontmatter.jiraKey,
15714
- jiraUrl: doc.frontmatter.jiraUrl
15714
+ jiraUrl: doc.frontmatter.jiraUrl,
15715
+ confluenceUrl: doc.frontmatter.confluenceUrl,
15716
+ confluenceTitle: doc.frontmatter.confluenceTitle
15715
15717
  };
15716
15718
  allItemsById.set(item.id, item);
15717
15719
  if (about && sprintItemIds.has(about)) {
@@ -19026,11 +19028,14 @@ import { tool as tool20 } from "@anthropic-ai/claude-agent-sdk";
19026
19028
  var JiraClient = class {
19027
19029
  baseUrl;
19028
19030
  baseUrlV3;
19031
+ confluenceBaseUrl;
19029
19032
  authHeader;
19033
+ host;
19030
19034
  constructor(config2) {
19031
- const host = config2.host.replace(/^https?:\/\//, "").replace(/\/+$/, "");
19032
- this.baseUrl = `https://${host}/rest/api/2`;
19033
- this.baseUrlV3 = `https://${host}/rest/api/3`;
19035
+ this.host = config2.host.replace(/^https?:\/\//, "").replace(/\/+$/, "");
19036
+ this.baseUrl = `https://${this.host}/rest/api/2`;
19037
+ this.baseUrlV3 = `https://${this.host}/rest/api/3`;
19038
+ this.confluenceBaseUrl = `https://${this.host}/wiki/api/v2`;
19034
19039
  this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
19035
19040
  }
19036
19041
  async request(path11, method = "GET", body) {
@@ -19118,6 +19123,30 @@ var JiraClient = class {
19118
19123
  { body }
19119
19124
  );
19120
19125
  }
19126
+ // --- Confluence methods ---
19127
+ async getConfluencePage(pageId) {
19128
+ return this.doRequest(
19129
+ `${this.confluenceBaseUrl}/pages/${encodeURIComponent(pageId)}?body-format=atlas_doc_format`,
19130
+ "GET"
19131
+ );
19132
+ }
19133
+ /**
19134
+ * Extract a Confluence page ID from various URL formats.
19135
+ * Returns null if the URL doesn't match any known pattern.
19136
+ */
19137
+ static extractPageId(url2) {
19138
+ const pagesMatch = url2.match(/\/pages\/(\d+)/);
19139
+ if (pagesMatch) return pagesMatch[1];
19140
+ const paramMatch = url2.match(/[?&]pageId=(\d+)/);
19141
+ if (paramMatch) return paramMatch[1];
19142
+ return null;
19143
+ }
19144
+ /**
19145
+ * Build a web URL for a Confluence page.
19146
+ */
19147
+ getConfluencePageUrl(pageId) {
19148
+ return `https://${this.host}/wiki/pages/viewpage.action?pageId=${pageId}`;
19149
+ }
19121
19150
  };
19122
19151
  function createJiraClient(jiraUserConfig) {
19123
19152
  const host = jiraUserConfig?.host ?? process.env.JIRA_HOST;
@@ -20178,6 +20207,111 @@ function createJiraTools(store, projectConfig) {
20178
20207
  };
20179
20208
  }
20180
20209
  ),
20210
+ // --- Confluence tools ---
20211
+ tool20(
20212
+ "link_to_confluence",
20213
+ "Link a Confluence page to any Marvin artifact. Validates the page exists and fetches its title.",
20214
+ {
20215
+ artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'D-001', 'A-003', 'T-002')"),
20216
+ confluenceUrl: external_exports.string().describe("Confluence page URL")
20217
+ },
20218
+ async (args) => {
20219
+ const jira = createJiraClient(jiraUserConfig);
20220
+ if (!jira) return jiraNotConfiguredError();
20221
+ const artifact = store.get(args.artifactId);
20222
+ if (!artifact) {
20223
+ return {
20224
+ content: [
20225
+ { type: "text", text: `Artifact ${args.artifactId} not found` }
20226
+ ],
20227
+ isError: true
20228
+ };
20229
+ }
20230
+ const pageId = JiraClient.extractPageId(args.confluenceUrl);
20231
+ if (!pageId) {
20232
+ return {
20233
+ content: [
20234
+ { type: "text", text: `Could not extract page ID from URL: ${args.confluenceUrl}` }
20235
+ ],
20236
+ isError: true
20237
+ };
20238
+ }
20239
+ const page = await jira.client.getConfluencePage(pageId);
20240
+ const existingTags = artifact.frontmatter.tags ?? [];
20241
+ store.update(args.artifactId, {
20242
+ confluenceUrl: args.confluenceUrl,
20243
+ confluencePageId: pageId,
20244
+ confluenceTitle: page.title,
20245
+ tags: [...existingTags.filter((t) => !t.startsWith("confluence:")), `confluence:${page.title}`]
20246
+ });
20247
+ return {
20248
+ content: [
20249
+ {
20250
+ type: "text",
20251
+ text: `Linked ${args.artifactId} to Confluence page "${page.title}" (ID: ${pageId}).`
20252
+ }
20253
+ ]
20254
+ };
20255
+ }
20256
+ ),
20257
+ tool20(
20258
+ "read_confluence_page",
20259
+ "Read the content of a Confluence page by URL or page ID. Returns the page title, metadata, and body as plain text.",
20260
+ {
20261
+ pageUrl: external_exports.string().optional().describe("Confluence page URL"),
20262
+ pageId: external_exports.string().optional().describe("Confluence page ID (alternative to URL)")
20263
+ },
20264
+ async (args) => {
20265
+ const jira = createJiraClient(jiraUserConfig);
20266
+ if (!jira) return jiraNotConfiguredError();
20267
+ const resolvedId = args.pageId ?? (args.pageUrl ? JiraClient.extractPageId(args.pageUrl) : null);
20268
+ if (!resolvedId) {
20269
+ return {
20270
+ content: [
20271
+ {
20272
+ type: "text",
20273
+ text: "Provide either pageUrl or pageId. Could not extract page ID from the given URL."
20274
+ }
20275
+ ],
20276
+ isError: true
20277
+ };
20278
+ }
20279
+ const page = await jira.client.getConfluencePage(resolvedId);
20280
+ let bodyText = "";
20281
+ if (page.body?.atlas_doc_format?.value) {
20282
+ try {
20283
+ const adf = JSON.parse(page.body.atlas_doc_format.value);
20284
+ bodyText = extractCommentText(adf);
20285
+ } catch {
20286
+ bodyText = page.body.atlas_doc_format.value;
20287
+ }
20288
+ }
20289
+ const parts = [
20290
+ `# ${page.title}`,
20291
+ "",
20292
+ `Page ID: ${page.id}`,
20293
+ `Status: ${page.status}`,
20294
+ `Version: ${page.version.number} (${page.version.createdAt.slice(0, 10)})`,
20295
+ "",
20296
+ "---",
20297
+ "",
20298
+ bodyText || "(empty page)"
20299
+ ];
20300
+ const allDocs = store.registeredTypes.flatMap((t) => store.list({ type: t }));
20301
+ const linkedArtifacts = allDocs.filter(
20302
+ (d) => d.frontmatter.confluencePageId === resolvedId || d.frontmatter.confluenceUrl === args.pageUrl
20303
+ );
20304
+ if (linkedArtifacts.length > 0) {
20305
+ parts.push("");
20306
+ parts.push("---");
20307
+ parts.push(`Linked Marvin artifacts: ${linkedArtifacts.map((d) => d.frontmatter.id).join(", ")}`);
20308
+ }
20309
+ return {
20310
+ content: [{ type: "text", text: parts.join("\n") }]
20311
+ };
20312
+ },
20313
+ { annotations: { readOnlyHint: true } }
20314
+ ),
20181
20315
  // --- Jira status fetch (read-only) ---
20182
20316
  tool20(
20183
20317
  "fetch_jira_status",
@@ -20488,6 +20622,8 @@ function formatIssueEntry(issue2) {
20488
20622
  var COMMON_TOOLS = `**Available tools:**
20489
20623
  - \`push_artifact_to_jira\` \u2014 create a Jira issue from any Marvin artifact and link it directly via \`jiraKey\` on the artifact.
20490
20624
  - \`link_to_jira\` \u2014 link an existing Jira issue to any Marvin artifact (sets \`jiraKey\` directly on the artifact).
20625
+ - \`link_to_confluence\` \u2014 link a Confluence page to any Marvin artifact. Validates the page exists and fetches its title.
20626
+ - \`read_confluence_page\` \u2014 **read-only**: fetch and return the content of a Confluence page by URL or page ID. Use this to review Confluence content for updating tasks, generating contributions, or answering questions.
20491
20627
  - \`fetch_jira_status\` \u2014 **read-only**: fetch current Jira status, subtask progress, and linked issues for Jira-linked actions/tasks. Returns proposed changes without applying them.
20492
20628
  - \`fetch_jira_daily\` \u2014 **read-only**: fetch a daily/range summary of all Jira changes \u2014 status transitions, comments, linked Confluence pages, and cross-references with Marvin artifacts. Returns proposed actions (status updates, unlinked issues, question candidates, Confluence pages to review).
20493
20629
  - \`fetch_jira_statuses\` \u2014 **read-only**: discover all Jira statuses in a project and show their Marvin mappings (mapped vs unmapped).
@@ -21188,11 +21324,30 @@ function formatDate(iso) {
21188
21324
  function typeLabel(type) {
21189
21325
  return type.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
21190
21326
  }
21327
+ var JIRA_SVG = `<svg class="integration-icon" viewBox="0 0 256 256" width="14" height="14" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="jg1" x1="98.03%" y1="0.16%" x2="58.89%" y2="40.87%"><stop offset="18%" stop-color="#0052CC"/><stop offset="100%" stop-color="#2684FF"/></linearGradient><linearGradient id="jg2" x1="100.17%" y1="0.05%" x2="55.76%" y2="45.19%"><stop offset="18%" stop-color="#0052CC"/><stop offset="100%" stop-color="#2684FF"/></linearGradient></defs><path d="M244.658 0H121.707a55.502 55.502 0 0 0 55.502 55.502h22.649V77.37c.02 30.625 24.841 55.447 55.466 55.502V10.666C255.324 4.777 250.55 0 244.658 0z" fill="#2684FF"/><path d="M183.822 61.262H60.872c.019 30.625 24.84 55.447 55.466 55.502h22.649v21.868c.02 30.597 24.798 55.408 55.395 55.502V71.928c0-5.891-4.776-10.666-10.56-10.666z" fill="url(#jg1)"/><path d="M122.951 122.489H0c0 30.653 24.85 55.502 55.502 55.502h22.72v21.868c.02 30.597 24.798 55.408 55.396 55.502V133.155c0-5.891-4.776-10.666-10.667-10.666z" fill="url(#jg2)"/></svg>`;
21328
+ var CONFLUENCE_SVG = `<svg class="integration-icon" viewBox="0 0 256 246" width="14" height="14" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cg1" x1="99.14%" y1="113.9%" x2="33.86%" y2="37.96%"><stop offset="18%" stop-color="#0052CC"/><stop offset="100%" stop-color="#2684FF"/></linearGradient><linearGradient id="cg2" x1="0.86%" y1="-13.9%" x2="66.14%" y2="62.04%"><stop offset="18%" stop-color="#0052CC"/><stop offset="100%" stop-color="#2684FF"/></linearGradient></defs><path d="M9.26 187.28c-3.14 5.06-6.71 10.98-9.26 15.53a7.84 7.84 0 0 0 2.83 10.72l67.58 40.48a7.85 7.85 0 0 0 10.72-2.63c2.14-3.54 5.01-8.25 8.15-13.41 22.18-36.47 44.67-32.02 85.83-13.41l68.59 31.05a7.85 7.85 0 0 0 10.42-3.94l29.24-66.24a7.85 7.85 0 0 0-3.84-10.32c-20.53-9.27-61.49-27.75-87.33-39.45-72.2-32.73-133.87-30.05-182.93 51.62z" fill="url(#cg1)"/><path d="M246.11 58.24c3.14-5.06 6.71-10.98 9.26-15.53a7.84 7.84 0 0 0-2.83-10.72L184.96 0a7.85 7.85 0 0 0-10.72 2.63c-2.14 3.54-5.01 8.25-8.15 13.41-22.18 36.47-44.67 32.02-85.83 13.41L12.37 -1.6a7.85 7.85 0 0 0-10.42 3.94L-27.29 68.58a7.85 7.85 0 0 0 3.84 10.32c20.53 9.27 61.49 27.75 87.33 39.45 72.2 32.73 133.87 30.05 182.23-60.11z" fill="url(#cg2)"/></svg>`;
21191
21329
  function jiraIcon(jiraKey, jiraUrl) {
21192
21330
  if (!jiraKey) return "";
21193
21331
  const href = jiraUrl ?? "#";
21194
21332
  const title = escapeHtml(jiraKey);
21195
- return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener" title="Jira: ${title}" class="jira-link"><svg class="jira-icon" viewBox="0 0 24 24" width="14" height="14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.005 2L6.5 7.505l5.505 5.505L17.51 7.505 12.005 2z" fill="#2684FF"/><path d="M6.5 7.505L1 13.01l5.505 5.505L12.01 13.01 6.5 7.505z" fill="url(#jira-g1)"/><path d="M17.51 7.505L12.005 13.01l5.505 5.505L23.015 13.01 17.51 7.505z" fill="url(#jira-g2)"/><path d="M12.005 13.01L6.5 18.515l5.505 5.505 5.505-5.505-5.505-5.505z" fill="#2684FF"/><defs><linearGradient id="jira-g1" x1="9.25" y1="7.51" x2="3.85" y2="12.91" gradientUnits="userSpaceOnUse"><stop stop-color="#0052CC"/><stop offset="1" stop-color="#2684FF"/></linearGradient><linearGradient id="jira-g2" x1="14.76" y1="7.51" x2="20.16" y2="12.91" gradientUnits="userSpaceOnUse"><stop stop-color="#0052CC"/><stop offset="1" stop-color="#2684FF"/></linearGradient></defs></svg></a>`;
21333
+ return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener" title="Jira: ${title}" class="integration-link jira-link">${JIRA_SVG}</a>`;
21334
+ }
21335
+ function confluenceIcon(confluenceUrl, confluenceTitle) {
21336
+ if (!confluenceUrl) return "";
21337
+ const title = confluenceTitle ? escapeHtml(confluenceTitle) : "Confluence";
21338
+ return `<a href="${escapeHtml(confluenceUrl)}" target="_blank" rel="noopener" title="${title}" class="integration-link confluence-link">${CONFLUENCE_SVG}</a>`;
21339
+ }
21340
+ function integrationIcons(frontmatter) {
21341
+ const jira = jiraIcon(
21342
+ frontmatter.jiraKey,
21343
+ frontmatter.jiraUrl
21344
+ );
21345
+ const confluence = confluenceIcon(
21346
+ frontmatter.confluenceUrl,
21347
+ frontmatter.confluenceTitle
21348
+ );
21349
+ if (!jira && !confluence) return "";
21350
+ return `<span class="integration-icons">${jira}${confluence}</span>`;
21196
21351
  }
21197
21352
  function renderMarkdown(md) {
21198
21353
  const lines = md.split("\n");
@@ -23059,17 +23214,22 @@ tr:hover td {
23059
23214
  .owner-badge-dm { background: rgba(52, 211, 153, 0.18); color: #34d399; }
23060
23215
  .owner-badge-other { background: rgba(139, 143, 164, 0.12); color: var(--text-dim); }
23061
23216
 
23062
- /* Jira link icon */
23063
- .jira-link {
23217
+ /* Integration icons (Jira, Confluence) */
23218
+ .integration-icons {
23064
23219
  display: inline-flex;
23065
23220
  align-items: center;
23221
+ gap: 0.2rem;
23066
23222
  vertical-align: middle;
23067
23223
  margin-left: 0.35rem;
23224
+ }
23225
+ .integration-link {
23226
+ display: inline-flex;
23227
+ align-items: center;
23068
23228
  opacity: 0.7;
23069
23229
  transition: opacity 0.15s;
23070
23230
  }
23071
- .jira-link:hover { opacity: 1; }
23072
- .jira-icon { vertical-align: middle; }
23231
+ .integration-link:hover { opacity: 1; }
23232
+ .integration-icon { vertical-align: middle; }
23073
23233
 
23074
23234
  /* Group header rows (PO dashboard decisions/deps) */
23075
23235
  .group-header-row td {
@@ -23095,7 +23255,7 @@ function documentsPage(data) {
23095
23255
  (doc) => `
23096
23256
  <tr>
23097
23257
  <td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.id)}</a></td>
23098
- <td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.title)}</a>${jiraIcon(doc.frontmatter.jiraKey, doc.frontmatter.jiraUrl)}</td>
23258
+ <td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.title)}</a>${integrationIcons(doc.frontmatter)}</td>
23099
23259
  <td>${statusBadge(doc.frontmatter.status)}</td>
23100
23260
  <td>${escapeHtml(doc.frontmatter.owner ?? "\u2014")}</td>
23101
23261
  <td>${doc.frontmatter.priority ? `<span class="priority-${doc.frontmatter.priority.toLowerCase()}">${escapeHtml(doc.frontmatter.priority)}</span>` : "\u2014"}</td>
@@ -23184,7 +23344,7 @@ function documentDetailPage(doc) {
23184
23344
  </div>
23185
23345
 
23186
23346
  <div class="page-header">
23187
- <h2>${escapeHtml(fm.title)}${jiraIcon(fm.jiraKey, fm.jiraUrl)}</h2>
23347
+ <h2>${escapeHtml(fm.title)}${integrationIcons(fm)}</h2>
23188
23348
  <div class="subtitle">${escapeHtml(fm.id)} &middot; ${escapeHtml(label)}</div>
23189
23349
  </div>
23190
23350
 
@@ -24615,7 +24775,7 @@ function renderItemRows(items, borderColor, showOwner, depth = 0) {
24615
24775
  const row = `
24616
24776
  <tr class="${classes.join(" ")}" style="--focus-color: ${borderColor}">
24617
24777
  <td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
24618
- <td>${escapeHtml(w.title)}${jiraIcon(w.jiraKey, w.jiraUrl)}</td>
24778
+ <td>${escapeHtml(w.title)}${jiraIcon(w.jiraKey, w.jiraUrl)}${confluenceIcon(w.confluenceUrl, w.confluenceTitle)}</td>
24619
24779
  ${ownerCell}
24620
24780
  <td>${statusBadge(w.status)}</td>
24621
24781
  <td>${progressCell}</td>
@@ -26240,7 +26400,7 @@ function boardPage(data, basePath = "/board") {
26240
26400
  <div class="board-card">
26241
26401
  <a href="/docs/${doc.frontmatter.type}/${doc.frontmatter.id}">
26242
26402
  <div class="bc-id">${escapeHtml(doc.frontmatter.id)}</div>
26243
- <div class="bc-title">${escapeHtml(doc.frontmatter.title)}${jiraIcon(doc.frontmatter.jiraKey, doc.frontmatter.jiraUrl)}</div>
26403
+ <div class="bc-title">${escapeHtml(doc.frontmatter.title)}${integrationIcons(doc.frontmatter)}</div>
26244
26404
  ${doc.frontmatter.owner ? `<div class="bc-owner">${escapeHtml(doc.frontmatter.owner)}</div>` : ""}
26245
26405
  </a>
26246
26406
  </div>`