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 +102 -68
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +101 -67
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +102 -68
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
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
|
|
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)} · ${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
|
|
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
|
|
25890
|
-
|
|
25891
|
-
|
|
25892
|
-
|
|
25893
|
-
|
|
25894
|
-
|
|
25895
|
-
|
|
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}.
|
|
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
|
|
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', '
|
|
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
|
-
- \`
|
|
26387
|
-
- \`
|
|
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
|
-
- \`
|
|
26394
|
-
- \`
|
|
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
|
|
28686
|
-
const
|
|
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:
|
|
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.
|
|
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
|
});
|