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.
@@ -15709,7 +15709,9 @@ function collectSprintSummaryData(store, sprintId) {
15709
15709
  progress: getEffectiveProgress(doc.frontmatter),
15710
15710
  owner: doc.frontmatter.owner,
15711
15711
  workFocus: focusTag ? focusTag.slice(6) : void 0,
15712
- aboutArtifact: about
15712
+ aboutArtifact: about,
15713
+ jiraKey: doc.frontmatter.jiraKey,
15714
+ jiraUrl: doc.frontmatter.jiraUrl
15713
15715
  };
15714
15716
  allItemsById.set(item.id, item);
15715
15717
  if (about && sprintItemIds.has(about)) {
@@ -19833,6 +19835,58 @@ function createJiraTools(store, projectConfig) {
19833
19835
  },
19834
19836
  { annotations: { readOnlyHint: true } }
19835
19837
  ),
19838
+ // --- Jira search (read-only) ---
19839
+ tool20(
19840
+ "search_jira",
19841
+ "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.",
19842
+ {
19843
+ jql: external_exports.string().describe(`JQL query (e.g. 'project = PROJ AND status = "In Progress"')`),
19844
+ maxResults: external_exports.number().optional().describe("Max issues to return (default 20)")
19845
+ },
19846
+ async (args) => {
19847
+ const jira = createJiraClient(jiraUserConfig);
19848
+ if (!jira) return jiraNotConfiguredError();
19849
+ const result = await jira.client.searchIssuesV3(
19850
+ args.jql,
19851
+ ["summary", "status", "issuetype", "priority", "assignee", "labels"],
19852
+ args.maxResults ?? 20
19853
+ );
19854
+ const allDocs = store.registeredTypes.flatMap((t) => store.list({ type: t }));
19855
+ const jiraKeyToArtifact = /* @__PURE__ */ new Map();
19856
+ for (const doc of allDocs) {
19857
+ const jk = doc.frontmatter.jiraKey;
19858
+ if (jk) jiraKeyToArtifact.set(jk, doc.frontmatter.id);
19859
+ }
19860
+ const issues = result.issues.map((issue2) => {
19861
+ const marvinId = jiraKeyToArtifact.get(issue2.key);
19862
+ return {
19863
+ key: issue2.key,
19864
+ summary: issue2.fields.summary,
19865
+ status: issue2.fields.status.name,
19866
+ issueType: issue2.fields.issuetype.name,
19867
+ priority: issue2.fields.priority?.name ?? "None",
19868
+ assignee: issue2.fields.assignee?.displayName ?? "unassigned",
19869
+ labels: issue2.fields.labels ?? [],
19870
+ marvinArtifact: marvinId ?? null
19871
+ };
19872
+ });
19873
+ const parts = [
19874
+ `Found ${result.total ?? issues.length} issues (showing ${issues.length}).`,
19875
+ ""
19876
+ ];
19877
+ for (const issue2 of issues) {
19878
+ const linked = issue2.marvinArtifact ? ` \u2192 ${issue2.marvinArtifact}` : " (not linked)";
19879
+ parts.push(`${issue2.key} \u2014 ${issue2.summary} [${issue2.status}]${linked}`);
19880
+ parts.push(` Type: ${issue2.issueType} | Priority: ${issue2.priority} | Assignee: ${issue2.assignee}`);
19881
+ }
19882
+ parts.push("");
19883
+ 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.");
19884
+ return {
19885
+ content: [{ type: "text", text: parts.join("\n") }]
19886
+ };
19887
+ },
19888
+ { annotations: { readOnlyHint: true } }
19889
+ ),
19836
19890
  // --- Jira → Local tools ---
19837
19891
  tool20(
19838
19892
  "pull_jira_issue",
@@ -19931,7 +19985,7 @@ function createJiraTools(store, projectConfig) {
19931
19985
  // --- Local → Jira tools ---
19932
19986
  tool20(
19933
19987
  "push_artifact_to_jira",
19934
- "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.",
19988
+ "Create a Jira issue from any Marvin artifact and link it directly via jiraKey on the artifact.",
19935
19989
  {
19936
19990
  artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'D-001', 'A-003', 'T-002')"),
19937
19991
  projectKey: external_exports.string().optional().describe("Jira project key (e.g. 'PROJ'). Falls back to jira.projectKey from .marvin/config.yaml if not provided."),
@@ -19974,46 +20028,18 @@ function createJiraTools(store, projectConfig) {
19974
20028
  description,
19975
20029
  issuetype: { name: args.issueType ?? "Task" }
19976
20030
  });
19977
- const isDirectLink = artifact.frontmatter.type === "action" || artifact.frontmatter.type === "task";
19978
- if (isDirectLink) {
19979
- const existingTags = artifact.frontmatter.tags ?? [];
19980
- store.update(args.artifactId, {
19981
- jiraKey: jiraResult.key,
19982
- jiraUrl: `https://${jira.host}/browse/${jiraResult.key}`,
19983
- lastJiraSyncAt: (/* @__PURE__ */ new Date()).toISOString(),
19984
- tags: [...existingTags.filter((t) => !t.startsWith("jira:")), `jira:${jiraResult.key}`]
19985
- });
19986
- return {
19987
- content: [
19988
- {
19989
- type: "text",
19990
- text: `Created Jira ${jiraResult.key} from ${args.artifactId}. Linked directly on the artifact.`
19991
- }
19992
- ]
19993
- };
19994
- }
19995
- const jiDoc = store.create(
19996
- JIRA_TYPE,
19997
- {
19998
- title: artifact.frontmatter.title,
19999
- status: "open",
20000
- jiraKey: jiraResult.key,
20001
- jiraUrl: `https://${jira.host}/browse/${jiraResult.key}`,
20002
- issueType: args.issueType ?? "Task",
20003
- priority: "Medium",
20004
- assignee: "",
20005
- labels: [],
20006
- linkedArtifacts: [args.artifactId],
20007
- tags: [`jira:${jiraResult.key}`],
20008
- lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
20009
- },
20010
- ""
20011
- );
20031
+ const existingTags = artifact.frontmatter.tags ?? [];
20032
+ store.update(args.artifactId, {
20033
+ jiraKey: jiraResult.key,
20034
+ jiraUrl: `https://${jira.host}/browse/${jiraResult.key}`,
20035
+ lastJiraSyncAt: (/* @__PURE__ */ new Date()).toISOString(),
20036
+ tags: [...existingTags.filter((t) => !t.startsWith("jira:")), `jira:${jiraResult.key}`]
20037
+ });
20012
20038
  return {
20013
20039
  content: [
20014
20040
  {
20015
20041
  type: "text",
20016
- text: `Created Jira ${jiraResult.key} from ${args.artifactId}. Tracking locally as ${jiDoc.frontmatter.id}.`
20042
+ text: `Created Jira ${jiraResult.key} from ${args.artifactId}. Linked directly on the artifact.`
20017
20043
  }
20018
20044
  ]
20019
20045
  };
@@ -20117,9 +20143,9 @@ function createJiraTools(store, projectConfig) {
20117
20143
  // --- Direct Jira linking for actions/tasks ---
20118
20144
  tool20(
20119
20145
  "link_to_jira",
20120
- "Link an existing Jira issue to a Marvin action or task (sets jiraKey directly on the artifact)",
20146
+ "Link an existing Jira issue to any Marvin artifact (sets jiraKey directly on the artifact)",
20121
20147
  {
20122
- artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'A-001', 'T-003')"),
20148
+ artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'A-001', 'D-003', 'T-002', 'Q-005')"),
20123
20149
  jiraKey: external_exports.string().describe("Jira issue key (e.g. 'PROJ-123')")
20124
20150
  },
20125
20151
  async (args) => {
@@ -20134,17 +20160,6 @@ function createJiraTools(store, projectConfig) {
20134
20160
  isError: true
20135
20161
  };
20136
20162
  }
20137
- if (artifact.frontmatter.type !== "action" && artifact.frontmatter.type !== "task") {
20138
- return {
20139
- content: [
20140
- {
20141
- type: "text",
20142
- 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.`
20143
- }
20144
- ],
20145
- isError: true
20146
- };
20147
- }
20148
20163
  const issue2 = await jira.client.getIssue(args.jiraKey);
20149
20164
  const existingTags = artifact.frontmatter.tags ?? [];
20150
20165
  store.update(args.artifactId, {
@@ -20471,15 +20486,16 @@ function formatIssueEntry(issue2) {
20471
20486
 
20472
20487
  // src/skills/builtin/jira/index.ts
20473
20488
  var COMMON_TOOLS = `**Available tools:**
20474
- - \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues (JI-xxx documents)
20475
- - \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
20476
- - \`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.
20477
- - \`link_to_jira\` \u2014 link an existing Jira issue to a Marvin action or task (sets \`jiraKey\` directly on the artifact)
20489
+ - \`push_artifact_to_jira\` \u2014 create a Jira issue from any Marvin artifact and link it directly via \`jiraKey\` on the artifact.
20490
+ - \`link_to_jira\` \u2014 link an existing Jira issue to any Marvin artifact (sets \`jiraKey\` directly on the artifact).
20478
20491
  - \`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.
20479
20492
  - \`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).
20480
20493
  - \`fetch_jira_statuses\` \u2014 **read-only**: discover all Jira statuses in a project and show their Marvin mappings (mapped vs unmapped).
20481
- - \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
20482
- - \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx`;
20494
+ - \`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.
20495
+ - \`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).
20496
+ - \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally imported JI-xxx documents.
20497
+ - \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira.
20498
+ - \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx document.`;
20483
20499
  var COMMON_WORKFLOW = `**Jira sync workflow:**
20484
20500
  1. Call \`fetch_jira_status\` to see what Jira reports for linked artifacts
20485
20501
  2. Analyze the proposed changes (status transitions, subtask progress, blockers from linked issues)
@@ -21172,6 +21188,12 @@ function formatDate(iso) {
21172
21188
  function typeLabel(type) {
21173
21189
  return type.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
21174
21190
  }
21191
+ function jiraIcon(jiraKey, jiraUrl) {
21192
+ if (!jiraKey) return "";
21193
+ const href = jiraUrl ?? "#";
21194
+ 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>`;
21196
+ }
21175
21197
  function renderMarkdown(md) {
21176
21198
  const lines = md.split("\n");
21177
21199
  const out = [];
@@ -23037,6 +23059,18 @@ tr:hover td {
23037
23059
  .owner-badge-dm { background: rgba(52, 211, 153, 0.18); color: #34d399; }
23038
23060
  .owner-badge-other { background: rgba(139, 143, 164, 0.12); color: var(--text-dim); }
23039
23061
 
23062
+ /* Jira link icon */
23063
+ .jira-link {
23064
+ display: inline-flex;
23065
+ align-items: center;
23066
+ vertical-align: middle;
23067
+ margin-left: 0.35rem;
23068
+ opacity: 0.7;
23069
+ transition: opacity 0.15s;
23070
+ }
23071
+ .jira-link:hover { opacity: 1; }
23072
+ .jira-icon { vertical-align: middle; }
23073
+
23040
23074
  /* Group header rows (PO dashboard decisions/deps) */
23041
23075
  .group-header-row td {
23042
23076
  background: var(--bg-hover);
@@ -23061,7 +23095,7 @@ function documentsPage(data) {
23061
23095
  (doc) => `
23062
23096
  <tr>
23063
23097
  <td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.id)}</a></td>
23064
- <td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.title)}</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>
23065
23099
  <td>${statusBadge(doc.frontmatter.status)}</td>
23066
23100
  <td>${escapeHtml(doc.frontmatter.owner ?? "\u2014")}</td>
23067
23101
  <td>${doc.frontmatter.priority ? `<span class="priority-${doc.frontmatter.priority.toLowerCase()}">${escapeHtml(doc.frontmatter.priority)}</span>` : "\u2014"}</td>
@@ -23150,7 +23184,7 @@ function documentDetailPage(doc) {
23150
23184
  </div>
23151
23185
 
23152
23186
  <div class="page-header">
23153
- <h2>${escapeHtml(fm.title)}</h2>
23187
+ <h2>${escapeHtml(fm.title)}${jiraIcon(fm.jiraKey, fm.jiraUrl)}</h2>
23154
23188
  <div class="subtitle">${escapeHtml(fm.id)} &middot; ${escapeHtml(label)}</div>
23155
23189
  </div>
23156
23190
 
@@ -24581,7 +24615,7 @@ function renderItemRows(items, borderColor, showOwner, depth = 0) {
24581
24615
  const row = `
24582
24616
  <tr class="${classes.join(" ")}" style="--focus-color: ${borderColor}">
24583
24617
  <td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
24584
- <td>${escapeHtml(w.title)}</td>
24618
+ <td>${escapeHtml(w.title)}${jiraIcon(w.jiraKey, w.jiraUrl)}</td>
24585
24619
  ${ownerCell}
24586
24620
  <td>${statusBadge(w.status)}</td>
24587
24621
  <td>${progressCell}</td>
@@ -26206,7 +26240,7 @@ function boardPage(data, basePath = "/board") {
26206
26240
  <div class="board-card">
26207
26241
  <a href="/docs/${doc.frontmatter.type}/${doc.frontmatter.id}">
26208
26242
  <div class="bc-id">${escapeHtml(doc.frontmatter.id)}</div>
26209
- <div class="bc-title">${escapeHtml(doc.frontmatter.title)}</div>
26243
+ <div class="bc-title">${escapeHtml(doc.frontmatter.title)}${jiraIcon(doc.frontmatter.jiraKey, doc.frontmatter.jiraUrl)}</div>
26210
26244
  ${doc.frontmatter.owner ? `<div class="bc-owner">${escapeHtml(doc.frontmatter.owner)}</div>` : ""}
26211
26245
  </a>
26212
26246
  </div>`
@@ -28112,22 +28146,22 @@ ${block.text}` };
28112
28146
  function collectTools(marvinDir) {
28113
28147
  const config2 = loadProjectConfig(marvinDir);
28114
28148
  const plugin = resolvePlugin(config2.methodology);
28115
- const registrations = plugin?.documentTypeRegistrations ?? [];
28116
- const store = new DocumentStore(marvinDir, registrations);
28149
+ const pluginRegistrations = plugin?.documentTypeRegistrations ?? [];
28150
+ const allSkills = loadAllSkills(marvinDir);
28151
+ const allSkillIds = [...allSkills.keys()];
28152
+ const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
28153
+ const store = new DocumentStore(marvinDir, [...pluginRegistrations, ...allSkillRegs]);
28117
28154
  const sourcesDir = path9.join(marvinDir, "sources");
28118
28155
  const hasSourcesDir = fs9.existsSync(sourcesDir);
28119
28156
  const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
28120
28157
  const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
28121
28158
  const sessionStore = new SessionStore(marvinDir);
28122
- const allSkills = loadAllSkills(marvinDir);
28123
- const allSkillIds = [...allSkills.keys()];
28124
28159
  const codeSkillTools = getSkillTools(allSkillIds, allSkills, store, config2);
28125
28160
  const skillsWithActions = allSkillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
28126
28161
  const projectRoot = path9.dirname(marvinDir);
28127
28162
  const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
28128
- const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
28129
28163
  const navGroups = buildNavGroups({
28130
- pluginRegs: registrations,
28164
+ pluginRegs: pluginRegistrations,
28131
28165
  skillRegs: allSkillRegs,
28132
28166
  pluginName: plugin?.name
28133
28167
  });