mrvn-cli 0.5.9 → 0.5.11

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
@@ -15773,7 +15773,9 @@ function collectSprintSummaryData(store, sprintId) {
15773
15773
  progress: getEffectiveProgress(doc.frontmatter),
15774
15774
  owner: doc.frontmatter.owner,
15775
15775
  workFocus: focusTag ? focusTag.slice(6) : void 0,
15776
- aboutArtifact: about
15776
+ aboutArtifact: about,
15777
+ jiraKey: doc.frontmatter.jiraKey,
15778
+ jiraUrl: doc.frontmatter.jiraUrl
15777
15779
  };
15778
15780
  allItemsById.set(item.id, item);
15779
15781
  if (about && sprintItemIds.has(about)) {
@@ -16284,6 +16286,12 @@ function formatDate(iso) {
16284
16286
  function typeLabel(type) {
16285
16287
  return type.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
16286
16288
  }
16289
+ function jiraIcon(jiraKey, jiraUrl) {
16290
+ if (!jiraKey) return "";
16291
+ const href = jiraUrl ?? "#";
16292
+ 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>`;
16294
+ }
16287
16295
  function renderMarkdown(md) {
16288
16296
  const lines = md.split("\n");
16289
16297
  const out = [];
@@ -18149,6 +18157,18 @@ tr:hover td {
18149
18157
  .owner-badge-dm { background: rgba(52, 211, 153, 0.18); color: #34d399; }
18150
18158
  .owner-badge-other { background: rgba(139, 143, 164, 0.12); color: var(--text-dim); }
18151
18159
 
18160
+ /* Jira link icon */
18161
+ .jira-link {
18162
+ display: inline-flex;
18163
+ align-items: center;
18164
+ vertical-align: middle;
18165
+ margin-left: 0.35rem;
18166
+ opacity: 0.7;
18167
+ transition: opacity 0.15s;
18168
+ }
18169
+ .jira-link:hover { opacity: 1; }
18170
+ .jira-icon { vertical-align: middle; }
18171
+
18152
18172
  /* Group header rows (PO dashboard decisions/deps) */
18153
18173
  .group-header-row td {
18154
18174
  background: var(--bg-hover);
@@ -18173,7 +18193,7 @@ function documentsPage(data) {
18173
18193
  (doc) => `
18174
18194
  <tr>
18175
18195
  <td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.id)}</a></td>
18176
- <td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.title)}</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>
18177
18197
  <td>${statusBadge(doc.frontmatter.status)}</td>
18178
18198
  <td>${escapeHtml(doc.frontmatter.owner ?? "\u2014")}</td>
18179
18199
  <td>${doc.frontmatter.priority ? `<span class="priority-${doc.frontmatter.priority.toLowerCase()}">${escapeHtml(doc.frontmatter.priority)}</span>` : "\u2014"}</td>
@@ -18262,7 +18282,7 @@ function documentDetailPage(doc) {
18262
18282
  </div>
18263
18283
 
18264
18284
  <div class="page-header">
18265
- <h2>${escapeHtml(fm.title)}</h2>
18285
+ <h2>${escapeHtml(fm.title)}${jiraIcon(fm.jiraKey, fm.jiraUrl)}</h2>
18266
18286
  <div class="subtitle">${escapeHtml(fm.id)} &middot; ${escapeHtml(label)}</div>
18267
18287
  </div>
18268
18288
 
@@ -19684,7 +19704,7 @@ function renderItemRows(items, borderColor, showOwner, depth = 0) {
19684
19704
  const row = `
19685
19705
  <tr class="${classes.join(" ")}" style="--focus-color: ${borderColor}">
19686
19706
  <td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
19687
- <td>${escapeHtml(w.title)}</td>
19707
+ <td>${escapeHtml(w.title)}${jiraIcon(w.jiraKey, w.jiraUrl)}</td>
19688
19708
  ${ownerCell}
19689
19709
  <td>${statusBadge(w.status)}</td>
19690
19710
  <td>${progressCell}</td>
@@ -21309,7 +21329,7 @@ function boardPage(data, basePath = "/board") {
21309
21329
  <div class="board-card">
21310
21330
  <a href="/docs/${doc.frontmatter.type}/${doc.frontmatter.id}">
21311
21331
  <div class="bc-id">${escapeHtml(doc.frontmatter.id)}</div>
21312
- <div class="bc-title">${escapeHtml(doc.frontmatter.title)}</div>
21332
+ <div class="bc-title">${escapeHtml(doc.frontmatter.title)}${jiraIcon(doc.frontmatter.jiraKey, doc.frontmatter.jiraUrl)}</div>
21313
21333
  ${doc.frontmatter.owner ? `<div class="bc-owner">${escapeHtml(doc.frontmatter.owner)}</div>` : ""}
21314
21334
  </a>
21315
21335
  </div>`
@@ -25745,6 +25765,58 @@ function createJiraTools(store, projectConfig) {
25745
25765
  },
25746
25766
  { annotations: { readOnlyHint: true } }
25747
25767
  ),
25768
+ // --- Jira search (read-only) ---
25769
+ tool20(
25770
+ "search_jira",
25771
+ "Search Jira issues via JQL query. Read-only \u2014 returns results without creating any local documents. Use this to preview before importing or to find issues for linking.",
25772
+ {
25773
+ jql: external_exports.string().describe(`JQL query (e.g. 'project = PROJ AND status = "In Progress"')`),
25774
+ maxResults: external_exports.number().optional().describe("Max issues to return (default 20)")
25775
+ },
25776
+ async (args) => {
25777
+ const jira = createJiraClient(jiraUserConfig);
25778
+ if (!jira) return jiraNotConfiguredError();
25779
+ const result = await jira.client.searchIssuesV3(
25780
+ args.jql,
25781
+ ["summary", "status", "issuetype", "priority", "assignee", "labels"],
25782
+ args.maxResults ?? 20
25783
+ );
25784
+ const allDocs = store.registeredTypes.flatMap((t) => store.list({ type: t }));
25785
+ const jiraKeyToArtifact = /* @__PURE__ */ new Map();
25786
+ for (const doc of allDocs) {
25787
+ const jk = doc.frontmatter.jiraKey;
25788
+ if (jk) jiraKeyToArtifact.set(jk, doc.frontmatter.id);
25789
+ }
25790
+ const issues = result.issues.map((issue2) => {
25791
+ const marvinId = jiraKeyToArtifact.get(issue2.key);
25792
+ return {
25793
+ key: issue2.key,
25794
+ summary: issue2.fields.summary,
25795
+ status: issue2.fields.status.name,
25796
+ issueType: issue2.fields.issuetype.name,
25797
+ priority: issue2.fields.priority?.name ?? "None",
25798
+ assignee: issue2.fields.assignee?.displayName ?? "unassigned",
25799
+ labels: issue2.fields.labels ?? [],
25800
+ marvinArtifact: marvinId ?? null
25801
+ };
25802
+ });
25803
+ const parts = [
25804
+ `Found ${result.total ?? issues.length} issues (showing ${issues.length}).`,
25805
+ ""
25806
+ ];
25807
+ for (const issue2 of issues) {
25808
+ const linked = issue2.marvinArtifact ? ` \u2192 ${issue2.marvinArtifact}` : " (not linked)";
25809
+ parts.push(`${issue2.key} \u2014 ${issue2.summary} [${issue2.status}]${linked}`);
25810
+ parts.push(` Type: ${issue2.issueType} | Priority: ${issue2.priority} | Assignee: ${issue2.assignee}`);
25811
+ }
25812
+ parts.push("");
25813
+ parts.push("This is read-only. Use link_to_jira to link issues to Marvin artifacts, or pull_jira_issue to import as JI-xxx documents.");
25814
+ return {
25815
+ content: [{ type: "text", text: parts.join("\n") }]
25816
+ };
25817
+ },
25818
+ { annotations: { readOnlyHint: true } }
25819
+ ),
25748
25820
  // --- Jira → Local tools ---
25749
25821
  tool20(
25750
25822
  "pull_jira_issue",
@@ -25843,7 +25915,7 @@ function createJiraTools(store, projectConfig) {
25843
25915
  // --- Local → Jira tools ---
25844
25916
  tool20(
25845
25917
  "push_artifact_to_jira",
25846
- "Create a Jira issue from a Marvin artifact. For actions/tasks, links directly via jiraKey on the artifact. For other types, creates a JI-xxx tracking document.",
25918
+ "Create a Jira issue from any Marvin artifact and link it directly via jiraKey on the artifact.",
25847
25919
  {
25848
25920
  artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'D-001', 'A-003', 'T-002')"),
25849
25921
  projectKey: external_exports.string().optional().describe("Jira project key (e.g. 'PROJ'). Falls back to jira.projectKey from .marvin/config.yaml if not provided."),
@@ -25886,46 +25958,18 @@ function createJiraTools(store, projectConfig) {
25886
25958
  description,
25887
25959
  issuetype: { name: args.issueType ?? "Task" }
25888
25960
  });
25889
- const isDirectLink = artifact.frontmatter.type === "action" || artifact.frontmatter.type === "task";
25890
- if (isDirectLink) {
25891
- const existingTags = artifact.frontmatter.tags ?? [];
25892
- store.update(args.artifactId, {
25893
- jiraKey: jiraResult.key,
25894
- jiraUrl: `https://${jira.host}/browse/${jiraResult.key}`,
25895
- lastJiraSyncAt: (/* @__PURE__ */ new Date()).toISOString(),
25896
- tags: [...existingTags.filter((t) => !t.startsWith("jira:")), `jira:${jiraResult.key}`]
25897
- });
25898
- return {
25899
- content: [
25900
- {
25901
- type: "text",
25902
- text: `Created Jira ${jiraResult.key} from ${args.artifactId}. Linked directly on the artifact.`
25903
- }
25904
- ]
25905
- };
25906
- }
25907
- const jiDoc = store.create(
25908
- JIRA_TYPE,
25909
- {
25910
- title: artifact.frontmatter.title,
25911
- status: "open",
25912
- jiraKey: jiraResult.key,
25913
- jiraUrl: `https://${jira.host}/browse/${jiraResult.key}`,
25914
- issueType: args.issueType ?? "Task",
25915
- priority: "Medium",
25916
- assignee: "",
25917
- labels: [],
25918
- linkedArtifacts: [args.artifactId],
25919
- tags: [`jira:${jiraResult.key}`],
25920
- lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
25921
- },
25922
- ""
25923
- );
25961
+ const existingTags = artifact.frontmatter.tags ?? [];
25962
+ store.update(args.artifactId, {
25963
+ jiraKey: jiraResult.key,
25964
+ jiraUrl: `https://${jira.host}/browse/${jiraResult.key}`,
25965
+ lastJiraSyncAt: (/* @__PURE__ */ new Date()).toISOString(),
25966
+ tags: [...existingTags.filter((t) => !t.startsWith("jira:")), `jira:${jiraResult.key}`]
25967
+ });
25924
25968
  return {
25925
25969
  content: [
25926
25970
  {
25927
25971
  type: "text",
25928
- text: `Created Jira ${jiraResult.key} from ${args.artifactId}. Tracking locally as ${jiDoc.frontmatter.id}.`
25972
+ text: `Created Jira ${jiraResult.key} from ${args.artifactId}. Linked directly on the artifact.`
25929
25973
  }
25930
25974
  ]
25931
25975
  };
@@ -26029,9 +26073,9 @@ function createJiraTools(store, projectConfig) {
26029
26073
  // --- Direct Jira linking for actions/tasks ---
26030
26074
  tool20(
26031
26075
  "link_to_jira",
26032
- "Link an existing Jira issue to a Marvin action or task (sets jiraKey directly on the artifact)",
26076
+ "Link an existing Jira issue to any Marvin artifact (sets jiraKey directly on the artifact)",
26033
26077
  {
26034
- artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'A-001', 'T-003')"),
26078
+ artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'A-001', 'D-003', 'T-002', 'Q-005')"),
26035
26079
  jiraKey: external_exports.string().describe("Jira issue key (e.g. 'PROJ-123')")
26036
26080
  },
26037
26081
  async (args) => {
@@ -26046,17 +26090,6 @@ function createJiraTools(store, projectConfig) {
26046
26090
  isError: true
26047
26091
  };
26048
26092
  }
26049
- if (artifact.frontmatter.type !== "action" && artifact.frontmatter.type !== "task") {
26050
- return {
26051
- content: [
26052
- {
26053
- type: "text",
26054
- text: `link_to_jira only supports action and task artifacts. ${args.artifactId} is type "${artifact.frontmatter.type}". Use link_artifact_to_jira for JI-xxx documents instead.`
26055
- }
26056
- ],
26057
- isError: true
26058
- };
26059
- }
26060
26093
  const issue2 = await jira.client.getIssue(args.jiraKey);
26061
26094
  const existingTags = artifact.frontmatter.tags ?? [];
26062
26095
  store.update(args.artifactId, {
@@ -26383,15 +26416,16 @@ function formatIssueEntry(issue2) {
26383
26416
 
26384
26417
  // src/skills/builtin/jira/index.ts
26385
26418
  var COMMON_TOOLS = `**Available tools:**
26386
- - \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues (JI-xxx documents)
26387
- - \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
26388
- - \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact. For **actions and tasks**, links directly via \`jiraKey\` on the artifact (no JI-xxx intermediary). For other types, creates a JI-xxx tracking document.
26389
- - \`link_to_jira\` \u2014 link an existing Jira issue to a Marvin action or task (sets \`jiraKey\` directly on the artifact)
26419
+ - \`push_artifact_to_jira\` \u2014 create a Jira issue from any Marvin artifact and link it directly via \`jiraKey\` on the artifact.
26420
+ - \`link_to_jira\` \u2014 link an existing Jira issue to any Marvin artifact (sets \`jiraKey\` directly on the artifact).
26390
26421
  - \`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.
26391
26422
  - \`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).
26392
26423
  - \`fetch_jira_statuses\` \u2014 **read-only**: discover all Jira statuses in a project and show their Marvin mappings (mapped vs unmapped).
26393
- - \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
26394
- - \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx`;
26424
+ - \`search_jira\` \u2014 **read-only**: search Jira via JQL and return results with Marvin cross-references. No documents created \u2014 use to preview before importing or find issues for linking.
26425
+ - \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import Jira issues as local JI-xxx documents (for Jira-originated items with no existing Marvin artifact).
26426
+ - \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally imported JI-xxx documents.
26427
+ - \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira.
26428
+ - \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx document.`;
26395
26429
  var COMMON_WORKFLOW = `**Jira sync workflow:**
26396
26430
  1. Call \`fetch_jira_status\` to see what Jira reports for linked artifacts
26397
26431
  2. Analyze the proposed changes (status transitions, subtask progress, blockers from linked issues)
@@ -28682,22 +28716,22 @@ ${block.text}` };
28682
28716
  function collectTools(marvinDir) {
28683
28717
  const config2 = loadProjectConfig(marvinDir);
28684
28718
  const plugin = resolvePlugin(config2.methodology);
28685
- const registrations = plugin?.documentTypeRegistrations ?? [];
28686
- const store = new DocumentStore(marvinDir, registrations);
28719
+ const pluginRegistrations = plugin?.documentTypeRegistrations ?? [];
28720
+ const allSkills = loadAllSkills(marvinDir);
28721
+ const allSkillIds = [...allSkills.keys()];
28722
+ const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
28723
+ const store = new DocumentStore(marvinDir, [...pluginRegistrations, ...allSkillRegs]);
28687
28724
  const sourcesDir = path11.join(marvinDir, "sources");
28688
28725
  const hasSourcesDir = fs11.existsSync(sourcesDir);
28689
28726
  const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
28690
28727
  const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
28691
28728
  const sessionStore = new SessionStore(marvinDir);
28692
- const allSkills = loadAllSkills(marvinDir);
28693
- const allSkillIds = [...allSkills.keys()];
28694
28729
  const codeSkillTools = getSkillTools(allSkillIds, allSkills, store, config2);
28695
28730
  const skillsWithActions = allSkillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
28696
28731
  const projectRoot = path11.dirname(marvinDir);
28697
28732
  const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
28698
- const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
28699
28733
  const navGroups = buildNavGroups({
28700
- pluginRegs: registrations,
28734
+ pluginRegs: pluginRegistrations,
28701
28735
  skillRegs: allSkillRegs,
28702
28736
  pluginName: plugin?.name
28703
28737
  });
@@ -31914,7 +31948,7 @@ function createProgram() {
31914
31948
  const program = new Command();
31915
31949
  program.name("marvin").description(
31916
31950
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
31917
- ).version("0.5.9");
31951
+ ).version("0.5.11");
31918
31952
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
31919
31953
  await initCommand();
31920
31954
  });