mrvn-cli 0.3.7 → 0.4.0
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/README.md +15 -13
- package/dist/index.js +117 -59
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +116 -58
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +117 -59
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin.js
CHANGED
|
@@ -14102,6 +14102,17 @@ function createMeetingTools(store) {
|
|
|
14102
14102
|
// src/plugins/builtin/tools/reports.ts
|
|
14103
14103
|
import { tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
|
|
14104
14104
|
|
|
14105
|
+
// src/plugins/builtin/tools/epic-utils.ts
|
|
14106
|
+
function normalizeLinkedFeatures(value) {
|
|
14107
|
+
if (value === void 0 || value === null) return [];
|
|
14108
|
+
if (typeof value === "string") return [value];
|
|
14109
|
+
if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
|
|
14110
|
+
return [];
|
|
14111
|
+
}
|
|
14112
|
+
function generateFeatureTags(features) {
|
|
14113
|
+
return features.map((id) => `feature:${id}`);
|
|
14114
|
+
}
|
|
14115
|
+
|
|
14105
14116
|
// src/reports/gar/collector.ts
|
|
14106
14117
|
function collectGarMetrics(store) {
|
|
14107
14118
|
const allActions = store.list({ type: "action" });
|
|
@@ -14599,7 +14610,7 @@ function createReportTools(store) {
|
|
|
14599
14610
|
id: epicDoc.frontmatter.id,
|
|
14600
14611
|
title: epicDoc.frontmatter.title,
|
|
14601
14612
|
status: epicDoc.frontmatter.status,
|
|
14602
|
-
linkedFeature: epicDoc.frontmatter.linkedFeature,
|
|
14613
|
+
linkedFeature: normalizeLinkedFeatures(epicDoc.frontmatter.linkedFeature),
|
|
14603
14614
|
targetDate: epicDoc.frontmatter.targetDate,
|
|
14604
14615
|
estimatedEffort: epicDoc.frontmatter.estimatedEffort,
|
|
14605
14616
|
workItems: {
|
|
@@ -14726,7 +14737,7 @@ function createReportTools(store) {
|
|
|
14726
14737
|
const epicDocs = store.list({ type: "epic" });
|
|
14727
14738
|
const features = featureDocs.filter((f) => !args.feature || f.frontmatter.id === args.feature).map((f) => {
|
|
14728
14739
|
const linkedEpics = epicDocs.filter(
|
|
14729
|
-
(e) => e.frontmatter.linkedFeature
|
|
14740
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).includes(f.frontmatter.id)
|
|
14730
14741
|
);
|
|
14731
14742
|
const byStatus = {};
|
|
14732
14743
|
for (const e of linkedEpics) {
|
|
@@ -14930,14 +14941,14 @@ function createEpicTools(store) {
|
|
|
14930
14941
|
let docs = store.list({ type: "epic", status: args.status });
|
|
14931
14942
|
if (args.linkedFeature) {
|
|
14932
14943
|
docs = docs.filter(
|
|
14933
|
-
(d) => d.frontmatter.linkedFeature
|
|
14944
|
+
(d) => normalizeLinkedFeatures(d.frontmatter.linkedFeature).includes(args.linkedFeature)
|
|
14934
14945
|
);
|
|
14935
14946
|
}
|
|
14936
14947
|
const summary = docs.map((d) => ({
|
|
14937
14948
|
id: d.frontmatter.id,
|
|
14938
14949
|
title: d.frontmatter.title,
|
|
14939
14950
|
status: d.frontmatter.status,
|
|
14940
|
-
linkedFeature: d.frontmatter.linkedFeature,
|
|
14951
|
+
linkedFeature: normalizeLinkedFeatures(d.frontmatter.linkedFeature),
|
|
14941
14952
|
owner: d.frontmatter.owner,
|
|
14942
14953
|
targetDate: d.frontmatter.targetDate,
|
|
14943
14954
|
estimatedEffort: d.frontmatter.estimatedEffort,
|
|
@@ -14978,11 +14989,11 @@ function createEpicTools(store) {
|
|
|
14978
14989
|
),
|
|
14979
14990
|
tool4(
|
|
14980
14991
|
"create_epic",
|
|
14981
|
-
"Create a new epic linked to
|
|
14992
|
+
"Create a new epic linked to one or more approved features. All linked features must exist and be approved.",
|
|
14982
14993
|
{
|
|
14983
14994
|
title: external_exports.string().describe("Epic title"),
|
|
14984
14995
|
content: external_exports.string().describe("Epic description and scope"),
|
|
14985
|
-
linkedFeature: external_exports.string().describe("Feature ID to link this epic to (e.g. 'F-001')"),
|
|
14996
|
+
linkedFeature: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]).describe("Feature ID(s) to link this epic to (e.g. 'F-001' or ['F-001', 'F-002'])"),
|
|
14986
14997
|
status: external_exports.enum(["planned", "in-progress", "done"]).optional().describe("Epic status (default: 'planned')"),
|
|
14987
14998
|
owner: external_exports.string().optional().describe("Epic owner"),
|
|
14988
14999
|
targetDate: external_exports.string().optional().describe("Target completion date (ISO format)"),
|
|
@@ -14990,45 +15001,48 @@ function createEpicTools(store) {
|
|
|
14990
15001
|
tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
|
|
14991
15002
|
},
|
|
14992
15003
|
async (args) => {
|
|
14993
|
-
const
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
|
|
14997
|
-
|
|
14998
|
-
|
|
14999
|
-
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
|
|
15003
|
-
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15004
|
+
const linkedFeatures = normalizeLinkedFeatures(args.linkedFeature);
|
|
15005
|
+
for (const featureId of linkedFeatures) {
|
|
15006
|
+
const feature = store.get(featureId);
|
|
15007
|
+
if (!feature) {
|
|
15008
|
+
return {
|
|
15009
|
+
content: [
|
|
15010
|
+
{
|
|
15011
|
+
type: "text",
|
|
15012
|
+
text: `Feature ${featureId} not found`
|
|
15013
|
+
}
|
|
15014
|
+
],
|
|
15015
|
+
isError: true
|
|
15016
|
+
};
|
|
15017
|
+
}
|
|
15018
|
+
if (feature.frontmatter.type !== "feature") {
|
|
15019
|
+
return {
|
|
15020
|
+
content: [
|
|
15021
|
+
{
|
|
15022
|
+
type: "text",
|
|
15023
|
+
text: `${featureId} is a ${feature.frontmatter.type}, not a feature`
|
|
15024
|
+
}
|
|
15025
|
+
],
|
|
15026
|
+
isError: true
|
|
15027
|
+
};
|
|
15028
|
+
}
|
|
15029
|
+
if (feature.frontmatter.status !== "approved") {
|
|
15030
|
+
return {
|
|
15031
|
+
content: [
|
|
15032
|
+
{
|
|
15033
|
+
type: "text",
|
|
15034
|
+
text: `Feature ${featureId} has status '${feature.frontmatter.status}'. Only approved features can have epics. Ask the Product Owner to approve it first.`
|
|
15035
|
+
}
|
|
15036
|
+
],
|
|
15037
|
+
isError: true
|
|
15038
|
+
};
|
|
15039
|
+
}
|
|
15026
15040
|
}
|
|
15027
15041
|
const frontmatter = {
|
|
15028
15042
|
title: args.title,
|
|
15029
15043
|
status: args.status ?? "planned",
|
|
15030
|
-
linkedFeature:
|
|
15031
|
-
tags: [
|
|
15044
|
+
linkedFeature: linkedFeatures,
|
|
15045
|
+
tags: [...generateFeatureTags(linkedFeatures), ...args.tags ?? []]
|
|
15032
15046
|
};
|
|
15033
15047
|
if (args.owner) frontmatter.owner = args.owner;
|
|
15034
15048
|
if (args.targetDate) frontmatter.targetDate = args.targetDate;
|
|
@@ -15038,7 +15052,7 @@ function createEpicTools(store) {
|
|
|
15038
15052
|
content: [
|
|
15039
15053
|
{
|
|
15040
15054
|
type: "text",
|
|
15041
|
-
text: `Created epic ${doc.frontmatter.id}: ${doc.frontmatter.title} (linked to ${
|
|
15055
|
+
text: `Created epic ${doc.frontmatter.id}: ${doc.frontmatter.title} (linked to ${linkedFeatures.join(", ")})`
|
|
15042
15056
|
}
|
|
15043
15057
|
]
|
|
15044
15058
|
};
|
|
@@ -15046,7 +15060,7 @@ function createEpicTools(store) {
|
|
|
15046
15060
|
),
|
|
15047
15061
|
tool4(
|
|
15048
15062
|
"update_epic",
|
|
15049
|
-
"Update an existing epic
|
|
15063
|
+
"Update an existing epic, including its linked features.",
|
|
15050
15064
|
{
|
|
15051
15065
|
id: external_exports.string().describe("Epic ID to update"),
|
|
15052
15066
|
title: external_exports.string().optional().describe("New title"),
|
|
@@ -15055,10 +15069,49 @@ function createEpicTools(store) {
|
|
|
15055
15069
|
owner: external_exports.string().optional().describe("New owner"),
|
|
15056
15070
|
targetDate: external_exports.string().optional().describe("New target date"),
|
|
15057
15071
|
estimatedEffort: external_exports.string().optional().describe("New estimated effort"),
|
|
15072
|
+
linkedFeature: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]).optional().describe("New linked feature ID(s)"),
|
|
15058
15073
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
15059
15074
|
},
|
|
15060
15075
|
async (args) => {
|
|
15061
|
-
const { id, content, ...updates } = args;
|
|
15076
|
+
const { id, content, linkedFeature: rawLinkedFeature, tags: userTags, ...updates } = args;
|
|
15077
|
+
if (rawLinkedFeature !== void 0) {
|
|
15078
|
+
const linkedFeatures = normalizeLinkedFeatures(rawLinkedFeature);
|
|
15079
|
+
for (const featureId of linkedFeatures) {
|
|
15080
|
+
const feature = store.get(featureId);
|
|
15081
|
+
if (!feature) {
|
|
15082
|
+
return {
|
|
15083
|
+
content: [
|
|
15084
|
+
{ type: "text", text: `Feature ${featureId} not found` }
|
|
15085
|
+
],
|
|
15086
|
+
isError: true
|
|
15087
|
+
};
|
|
15088
|
+
}
|
|
15089
|
+
if (feature.frontmatter.type !== "feature") {
|
|
15090
|
+
return {
|
|
15091
|
+
content: [
|
|
15092
|
+
{ type: "text", text: `${featureId} is a ${feature.frontmatter.type}, not a feature` }
|
|
15093
|
+
],
|
|
15094
|
+
isError: true
|
|
15095
|
+
};
|
|
15096
|
+
}
|
|
15097
|
+
if (feature.frontmatter.status !== "approved") {
|
|
15098
|
+
return {
|
|
15099
|
+
content: [
|
|
15100
|
+
{ type: "text", text: `Feature ${featureId} has status '${feature.frontmatter.status}'. Only approved features can have epics. Ask the Product Owner to approve it first.` }
|
|
15101
|
+
],
|
|
15102
|
+
isError: true
|
|
15103
|
+
};
|
|
15104
|
+
}
|
|
15105
|
+
}
|
|
15106
|
+
updates.linkedFeature = linkedFeatures;
|
|
15107
|
+
const existingDoc = store.get(id);
|
|
15108
|
+
const existingTags = existingDoc?.frontmatter.tags ?? [];
|
|
15109
|
+
const nonFeatureTags = existingTags.filter((t) => !t.startsWith("feature:"));
|
|
15110
|
+
const baseTags = userTags ?? nonFeatureTags;
|
|
15111
|
+
updates.tags = [...generateFeatureTags(linkedFeatures), ...baseTags];
|
|
15112
|
+
} else if (userTags !== void 0) {
|
|
15113
|
+
updates.tags = userTags;
|
|
15114
|
+
}
|
|
15062
15115
|
const doc = store.update(id, updates, content);
|
|
15063
15116
|
return {
|
|
15064
15117
|
content: [
|
|
@@ -15393,7 +15446,9 @@ function createSprintPlanningTools(store) {
|
|
|
15393
15446
|
const questions = store.list({ type: "question", status: "open" });
|
|
15394
15447
|
const contributions = store.list({ type: "contribution" });
|
|
15395
15448
|
const approvedFeatures = features.filter((f) => f.frontmatter.status === "approved").sort((a, b) => priorityRank(a.frontmatter.priority) - priorityRank(b.frontmatter.priority)).map((f) => {
|
|
15396
|
-
const linkedEpics = epics.filter(
|
|
15449
|
+
const linkedEpics = epics.filter(
|
|
15450
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).includes(f.frontmatter.id)
|
|
15451
|
+
);
|
|
15397
15452
|
const epicsByStatus = {};
|
|
15398
15453
|
for (const e of linkedEpics) {
|
|
15399
15454
|
epicsByStatus[e.frontmatter.status] = (epicsByStatus[e.frontmatter.status] ?? 0) + 1;
|
|
@@ -15418,22 +15473,25 @@ function createSprintPlanningTools(store) {
|
|
|
15418
15473
|
);
|
|
15419
15474
|
if (args.focusFeature) {
|
|
15420
15475
|
backlogEpics = backlogEpics.filter(
|
|
15421
|
-
(e) => e.frontmatter.linkedFeature
|
|
15476
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).includes(args.focusFeature)
|
|
15422
15477
|
);
|
|
15423
15478
|
}
|
|
15424
15479
|
const backlog = backlogEpics.sort((a, b) => {
|
|
15425
|
-
const
|
|
15426
|
-
const
|
|
15427
|
-
|
|
15480
|
+
const aFeatures = normalizeLinkedFeatures(a.frontmatter.linkedFeature);
|
|
15481
|
+
const bFeatures = normalizeLinkedFeatures(b.frontmatter.linkedFeature);
|
|
15482
|
+
const aRank = Math.min(...aFeatures.map((id) => priorityRank(featureMap.get(id)?.frontmatter.priority)), 99);
|
|
15483
|
+
const bRank = Math.min(...bFeatures.map((id) => priorityRank(featureMap.get(id)?.frontmatter.priority)), 99);
|
|
15484
|
+
return aRank - bRank;
|
|
15428
15485
|
}).map((e) => {
|
|
15429
|
-
const
|
|
15486
|
+
const linkedFeatures = normalizeLinkedFeatures(e.frontmatter.linkedFeature);
|
|
15487
|
+
const parents = linkedFeatures.map((id) => featureMap.get(id)).filter(Boolean);
|
|
15430
15488
|
return {
|
|
15431
15489
|
id: e.frontmatter.id,
|
|
15432
15490
|
title: e.frontmatter.title,
|
|
15433
15491
|
status: e.frontmatter.status,
|
|
15434
|
-
linkedFeature:
|
|
15435
|
-
featureTitle:
|
|
15436
|
-
featurePriority:
|
|
15492
|
+
linkedFeature: linkedFeatures,
|
|
15493
|
+
featureTitle: parents.map((p) => p.frontmatter.title).join(", ") || null,
|
|
15494
|
+
featurePriority: parents.map((p) => p.frontmatter.priority).join(", ") || null,
|
|
15437
15495
|
estimatedEffort: e.frontmatter.estimatedEffort ?? null,
|
|
15438
15496
|
targetDate: e.frontmatter.targetDate ?? null
|
|
15439
15497
|
};
|
|
@@ -15514,8 +15572,8 @@ function createSprintPlanningTools(store) {
|
|
|
15514
15572
|
const epicsAtRisk = epics.filter((e) => {
|
|
15515
15573
|
if (e.frontmatter.status === "done") return false;
|
|
15516
15574
|
if (e.frontmatter.targetDate && e.frontmatter.targetDate < now) return true;
|
|
15517
|
-
const
|
|
15518
|
-
if (
|
|
15575
|
+
const linkedIds = normalizeLinkedFeatures(e.frontmatter.linkedFeature);
|
|
15576
|
+
if (linkedIds.some((id) => featureMap.get(id)?.frontmatter.status === "deferred")) return true;
|
|
15519
15577
|
return false;
|
|
15520
15578
|
}).map((e) => ({
|
|
15521
15579
|
id: e.frontmatter.id,
|
|
@@ -18049,7 +18107,7 @@ function getDiagramData(store) {
|
|
|
18049
18107
|
id: fm.id,
|
|
18050
18108
|
title: fm.title,
|
|
18051
18109
|
status: fm.status,
|
|
18052
|
-
linkedFeature: fm.linkedFeature
|
|
18110
|
+
linkedFeature: normalizeLinkedFeatures(fm.linkedFeature)
|
|
18053
18111
|
});
|
|
18054
18112
|
break;
|
|
18055
18113
|
case "feature":
|
|
@@ -18840,8 +18898,8 @@ function buildArtifactFlowchart(data) {
|
|
|
18840
18898
|
lines.push(" classDef default fill:#1e293b,stroke:#475569,color:#e2e8f0");
|
|
18841
18899
|
const nodeIds = /* @__PURE__ */ new Set();
|
|
18842
18900
|
for (const epic of data.epics) {
|
|
18843
|
-
|
|
18844
|
-
const feature = data.features.find((f) => f.id ===
|
|
18901
|
+
for (const featureId of epic.linkedFeature) {
|
|
18902
|
+
const feature = data.features.find((f) => f.id === featureId);
|
|
18845
18903
|
if (feature) {
|
|
18846
18904
|
const fNode = feature.id.replace(/-/g, "_");
|
|
18847
18905
|
const eNode = epic.id.replace(/-/g, "_");
|
|
@@ -23644,7 +23702,7 @@ function createProgram() {
|
|
|
23644
23702
|
const program2 = new Command();
|
|
23645
23703
|
program2.name("marvin").description(
|
|
23646
23704
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
23647
|
-
).version("0.
|
|
23705
|
+
).version("0.4.0");
|
|
23648
23706
|
program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
23649
23707
|
await initCommand();
|
|
23650
23708
|
});
|