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.
package/dist/index.js CHANGED
@@ -15775,7 +15775,9 @@ function collectSprintSummaryData(store, sprintId) {
15775
15775
  workFocus: focusTag ? focusTag.slice(6) : void 0,
15776
15776
  aboutArtifact: about,
15777
15777
  jiraKey: doc.frontmatter.jiraKey,
15778
- jiraUrl: doc.frontmatter.jiraUrl
15778
+ jiraUrl: doc.frontmatter.jiraUrl,
15779
+ confluenceUrl: doc.frontmatter.confluenceUrl,
15780
+ confluenceTitle: doc.frontmatter.confluenceTitle
15779
15781
  };
15780
15782
  allItemsById.set(item.id, item);
15781
15783
  if (about && sprintItemIds.has(about)) {
@@ -16286,11 +16288,30 @@ function formatDate(iso) {
16286
16288
  function typeLabel(type) {
16287
16289
  return type.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
16288
16290
  }
16291
+ 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>`;
16292
+ 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>`;
16289
16293
  function jiraIcon(jiraKey, jiraUrl) {
16290
16294
  if (!jiraKey) return "";
16291
16295
  const href = jiraUrl ?? "#";
16292
16296
  const title = escapeHtml(jiraKey);
16293
- 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>`;
16297
+ return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener" title="Jira: ${title}" class="integration-link jira-link">${JIRA_SVG}</a>`;
16298
+ }
16299
+ function confluenceIcon(confluenceUrl, confluenceTitle) {
16300
+ if (!confluenceUrl) return "";
16301
+ const title = confluenceTitle ? escapeHtml(confluenceTitle) : "Confluence";
16302
+ return `<a href="${escapeHtml(confluenceUrl)}" target="_blank" rel="noopener" title="${title}" class="integration-link confluence-link">${CONFLUENCE_SVG}</a>`;
16303
+ }
16304
+ function integrationIcons(frontmatter) {
16305
+ const jira = jiraIcon(
16306
+ frontmatter.jiraKey,
16307
+ frontmatter.jiraUrl
16308
+ );
16309
+ const confluence = confluenceIcon(
16310
+ frontmatter.confluenceUrl,
16311
+ frontmatter.confluenceTitle
16312
+ );
16313
+ if (!jira && !confluence) return "";
16314
+ return `<span class="integration-icons">${jira}${confluence}</span>`;
16294
16315
  }
16295
16316
  function renderMarkdown(md) {
16296
16317
  const lines = md.split("\n");
@@ -18157,17 +18178,22 @@ tr:hover td {
18157
18178
  .owner-badge-dm { background: rgba(52, 211, 153, 0.18); color: #34d399; }
18158
18179
  .owner-badge-other { background: rgba(139, 143, 164, 0.12); color: var(--text-dim); }
18159
18180
 
18160
- /* Jira link icon */
18161
- .jira-link {
18181
+ /* Integration icons (Jira, Confluence) */
18182
+ .integration-icons {
18162
18183
  display: inline-flex;
18163
18184
  align-items: center;
18185
+ gap: 0.2rem;
18164
18186
  vertical-align: middle;
18165
18187
  margin-left: 0.35rem;
18188
+ }
18189
+ .integration-link {
18190
+ display: inline-flex;
18191
+ align-items: center;
18166
18192
  opacity: 0.7;
18167
18193
  transition: opacity 0.15s;
18168
18194
  }
18169
- .jira-link:hover { opacity: 1; }
18170
- .jira-icon { vertical-align: middle; }
18195
+ .integration-link:hover { opacity: 1; }
18196
+ .integration-icon { vertical-align: middle; }
18171
18197
 
18172
18198
  /* Group header rows (PO dashboard decisions/deps) */
18173
18199
  .group-header-row td {
@@ -18193,7 +18219,7 @@ function documentsPage(data) {
18193
18219
  (doc) => `
18194
18220
  <tr>
18195
18221
  <td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.id)}</a></td>
18196
- <td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.title)}</a>${jiraIcon(doc.frontmatter.jiraKey, doc.frontmatter.jiraUrl)}</td>
18222
+ <td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.title)}</a>${integrationIcons(doc.frontmatter)}</td>
18197
18223
  <td>${statusBadge(doc.frontmatter.status)}</td>
18198
18224
  <td>${escapeHtml(doc.frontmatter.owner ?? "\u2014")}</td>
18199
18225
  <td>${doc.frontmatter.priority ? `<span class="priority-${doc.frontmatter.priority.toLowerCase()}">${escapeHtml(doc.frontmatter.priority)}</span>` : "\u2014"}</td>
@@ -18282,7 +18308,7 @@ function documentDetailPage(doc) {
18282
18308
  </div>
18283
18309
 
18284
18310
  <div class="page-header">
18285
- <h2>${escapeHtml(fm.title)}${jiraIcon(fm.jiraKey, fm.jiraUrl)}</h2>
18311
+ <h2>${escapeHtml(fm.title)}${integrationIcons(fm)}</h2>
18286
18312
  <div class="subtitle">${escapeHtml(fm.id)} &middot; ${escapeHtml(label)}</div>
18287
18313
  </div>
18288
18314
 
@@ -19704,7 +19730,7 @@ function renderItemRows(items, borderColor, showOwner, depth = 0) {
19704
19730
  const row = `
19705
19731
  <tr class="${classes.join(" ")}" style="--focus-color: ${borderColor}">
19706
19732
  <td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
19707
- <td>${escapeHtml(w.title)}${jiraIcon(w.jiraKey, w.jiraUrl)}</td>
19733
+ <td>${escapeHtml(w.title)}${jiraIcon(w.jiraKey, w.jiraUrl)}${confluenceIcon(w.confluenceUrl, w.confluenceTitle)}</td>
19708
19734
  ${ownerCell}
19709
19735
  <td>${statusBadge(w.status)}</td>
19710
19736
  <td>${progressCell}</td>
@@ -21329,7 +21355,7 @@ function boardPage(data, basePath = "/board") {
21329
21355
  <div class="board-card">
21330
21356
  <a href="/docs/${doc.frontmatter.type}/${doc.frontmatter.id}">
21331
21357
  <div class="bc-id">${escapeHtml(doc.frontmatter.id)}</div>
21332
- <div class="bc-title">${escapeHtml(doc.frontmatter.title)}${jiraIcon(doc.frontmatter.jiraKey, doc.frontmatter.jiraUrl)}</div>
21358
+ <div class="bc-title">${escapeHtml(doc.frontmatter.title)}${integrationIcons(doc.frontmatter)}</div>
21333
21359
  ${doc.frontmatter.owner ? `<div class="bc-owner">${escapeHtml(doc.frontmatter.owner)}</div>` : ""}
21334
21360
  </a>
21335
21361
  </div>`
@@ -24916,11 +24942,14 @@ import { tool as tool20 } from "@anthropic-ai/claude-agent-sdk";
24916
24942
  var JiraClient = class {
24917
24943
  baseUrl;
24918
24944
  baseUrlV3;
24945
+ confluenceBaseUrl;
24919
24946
  authHeader;
24947
+ host;
24920
24948
  constructor(config2) {
24921
- const host = config2.host.replace(/^https?:\/\//, "").replace(/\/+$/, "");
24922
- this.baseUrl = `https://${host}/rest/api/2`;
24923
- this.baseUrlV3 = `https://${host}/rest/api/3`;
24949
+ this.host = config2.host.replace(/^https?:\/\//, "").replace(/\/+$/, "");
24950
+ this.baseUrl = `https://${this.host}/rest/api/2`;
24951
+ this.baseUrlV3 = `https://${this.host}/rest/api/3`;
24952
+ this.confluenceBaseUrl = `https://${this.host}/wiki/api/v2`;
24924
24953
  this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
24925
24954
  }
24926
24955
  async request(path21, method = "GET", body) {
@@ -25008,6 +25037,30 @@ var JiraClient = class {
25008
25037
  { body }
25009
25038
  );
25010
25039
  }
25040
+ // --- Confluence methods ---
25041
+ async getConfluencePage(pageId) {
25042
+ return this.doRequest(
25043
+ `${this.confluenceBaseUrl}/pages/${encodeURIComponent(pageId)}?body-format=atlas_doc_format`,
25044
+ "GET"
25045
+ );
25046
+ }
25047
+ /**
25048
+ * Extract a Confluence page ID from various URL formats.
25049
+ * Returns null if the URL doesn't match any known pattern.
25050
+ */
25051
+ static extractPageId(url2) {
25052
+ const pagesMatch = url2.match(/\/pages\/(\d+)/);
25053
+ if (pagesMatch) return pagesMatch[1];
25054
+ const paramMatch = url2.match(/[?&]pageId=(\d+)/);
25055
+ if (paramMatch) return paramMatch[1];
25056
+ return null;
25057
+ }
25058
+ /**
25059
+ * Build a web URL for a Confluence page.
25060
+ */
25061
+ getConfluencePageUrl(pageId) {
25062
+ return `https://${this.host}/wiki/pages/viewpage.action?pageId=${pageId}`;
25063
+ }
25011
25064
  };
25012
25065
  function createJiraClient(jiraUserConfig) {
25013
25066
  const host = jiraUserConfig?.host ?? process.env.JIRA_HOST;
@@ -26108,6 +26161,111 @@ function createJiraTools(store, projectConfig) {
26108
26161
  };
26109
26162
  }
26110
26163
  ),
26164
+ // --- Confluence tools ---
26165
+ tool20(
26166
+ "link_to_confluence",
26167
+ "Link a Confluence page to any Marvin artifact. Validates the page exists and fetches its title.",
26168
+ {
26169
+ artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'D-001', 'A-003', 'T-002')"),
26170
+ confluenceUrl: external_exports.string().describe("Confluence page URL")
26171
+ },
26172
+ async (args) => {
26173
+ const jira = createJiraClient(jiraUserConfig);
26174
+ if (!jira) return jiraNotConfiguredError();
26175
+ const artifact = store.get(args.artifactId);
26176
+ if (!artifact) {
26177
+ return {
26178
+ content: [
26179
+ { type: "text", text: `Artifact ${args.artifactId} not found` }
26180
+ ],
26181
+ isError: true
26182
+ };
26183
+ }
26184
+ const pageId = JiraClient.extractPageId(args.confluenceUrl);
26185
+ if (!pageId) {
26186
+ return {
26187
+ content: [
26188
+ { type: "text", text: `Could not extract page ID from URL: ${args.confluenceUrl}` }
26189
+ ],
26190
+ isError: true
26191
+ };
26192
+ }
26193
+ const page = await jira.client.getConfluencePage(pageId);
26194
+ const existingTags = artifact.frontmatter.tags ?? [];
26195
+ store.update(args.artifactId, {
26196
+ confluenceUrl: args.confluenceUrl,
26197
+ confluencePageId: pageId,
26198
+ confluenceTitle: page.title,
26199
+ tags: [...existingTags.filter((t) => !t.startsWith("confluence:")), `confluence:${page.title}`]
26200
+ });
26201
+ return {
26202
+ content: [
26203
+ {
26204
+ type: "text",
26205
+ text: `Linked ${args.artifactId} to Confluence page "${page.title}" (ID: ${pageId}).`
26206
+ }
26207
+ ]
26208
+ };
26209
+ }
26210
+ ),
26211
+ tool20(
26212
+ "read_confluence_page",
26213
+ "Read the content of a Confluence page by URL or page ID. Returns the page title, metadata, and body as plain text.",
26214
+ {
26215
+ pageUrl: external_exports.string().optional().describe("Confluence page URL"),
26216
+ pageId: external_exports.string().optional().describe("Confluence page ID (alternative to URL)")
26217
+ },
26218
+ async (args) => {
26219
+ const jira = createJiraClient(jiraUserConfig);
26220
+ if (!jira) return jiraNotConfiguredError();
26221
+ const resolvedId = args.pageId ?? (args.pageUrl ? JiraClient.extractPageId(args.pageUrl) : null);
26222
+ if (!resolvedId) {
26223
+ return {
26224
+ content: [
26225
+ {
26226
+ type: "text",
26227
+ text: "Provide either pageUrl or pageId. Could not extract page ID from the given URL."
26228
+ }
26229
+ ],
26230
+ isError: true
26231
+ };
26232
+ }
26233
+ const page = await jira.client.getConfluencePage(resolvedId);
26234
+ let bodyText = "";
26235
+ if (page.body?.atlas_doc_format?.value) {
26236
+ try {
26237
+ const adf = JSON.parse(page.body.atlas_doc_format.value);
26238
+ bodyText = extractCommentText(adf);
26239
+ } catch {
26240
+ bodyText = page.body.atlas_doc_format.value;
26241
+ }
26242
+ }
26243
+ const parts = [
26244
+ `# ${page.title}`,
26245
+ "",
26246
+ `Page ID: ${page.id}`,
26247
+ `Status: ${page.status}`,
26248
+ `Version: ${page.version.number} (${page.version.createdAt.slice(0, 10)})`,
26249
+ "",
26250
+ "---",
26251
+ "",
26252
+ bodyText || "(empty page)"
26253
+ ];
26254
+ const allDocs = store.registeredTypes.flatMap((t) => store.list({ type: t }));
26255
+ const linkedArtifacts = allDocs.filter(
26256
+ (d) => d.frontmatter.confluencePageId === resolvedId || d.frontmatter.confluenceUrl === args.pageUrl
26257
+ );
26258
+ if (linkedArtifacts.length > 0) {
26259
+ parts.push("");
26260
+ parts.push("---");
26261
+ parts.push(`Linked Marvin artifacts: ${linkedArtifacts.map((d) => d.frontmatter.id).join(", ")}`);
26262
+ }
26263
+ return {
26264
+ content: [{ type: "text", text: parts.join("\n") }]
26265
+ };
26266
+ },
26267
+ { annotations: { readOnlyHint: true } }
26268
+ ),
26111
26269
  // --- Jira status fetch (read-only) ---
26112
26270
  tool20(
26113
26271
  "fetch_jira_status",
@@ -26418,6 +26576,8 @@ function formatIssueEntry(issue2) {
26418
26576
  var COMMON_TOOLS = `**Available tools:**
26419
26577
  - \`push_artifact_to_jira\` \u2014 create a Jira issue from any Marvin artifact and link it directly via \`jiraKey\` on the artifact.
26420
26578
  - \`link_to_jira\` \u2014 link an existing Jira issue to any Marvin artifact (sets \`jiraKey\` directly on the artifact).
26579
+ - \`link_to_confluence\` \u2014 link a Confluence page to any Marvin artifact. Validates the page exists and fetches its title.
26580
+ - \`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.
26421
26581
  - \`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.
26422
26582
  - \`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).
26423
26583
  - \`fetch_jira_statuses\` \u2014 **read-only**: discover all Jira statuses in a project and show their Marvin mappings (mapped vs unmapped).
@@ -31948,7 +32108,7 @@ function createProgram() {
31948
32108
  const program = new Command();
31949
32109
  program.name("marvin").description(
31950
32110
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
31951
- ).version("0.5.11");
32111
+ ).version("0.5.12");
31952
32112
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
31953
32113
  await initCommand();
31954
32114
  });