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-serve.js
CHANGED
|
@@ -15089,6 +15089,17 @@ function createMeetingTools(store) {
|
|
|
15089
15089
|
// src/plugins/builtin/tools/reports.ts
|
|
15090
15090
|
import { tool as tool8 } from "@anthropic-ai/claude-agent-sdk";
|
|
15091
15091
|
|
|
15092
|
+
// src/plugins/builtin/tools/epic-utils.ts
|
|
15093
|
+
function normalizeLinkedFeatures(value) {
|
|
15094
|
+
if (value === void 0 || value === null) return [];
|
|
15095
|
+
if (typeof value === "string") return [value];
|
|
15096
|
+
if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
|
|
15097
|
+
return [];
|
|
15098
|
+
}
|
|
15099
|
+
function generateFeatureTags(features) {
|
|
15100
|
+
return features.map((id) => `feature:${id}`);
|
|
15101
|
+
}
|
|
15102
|
+
|
|
15092
15103
|
// src/reports/gar/collector.ts
|
|
15093
15104
|
function collectGarMetrics(store) {
|
|
15094
15105
|
const allActions = store.list({ type: "action" });
|
|
@@ -15586,7 +15597,7 @@ function createReportTools(store) {
|
|
|
15586
15597
|
id: epicDoc.frontmatter.id,
|
|
15587
15598
|
title: epicDoc.frontmatter.title,
|
|
15588
15599
|
status: epicDoc.frontmatter.status,
|
|
15589
|
-
linkedFeature: epicDoc.frontmatter.linkedFeature,
|
|
15600
|
+
linkedFeature: normalizeLinkedFeatures(epicDoc.frontmatter.linkedFeature),
|
|
15590
15601
|
targetDate: epicDoc.frontmatter.targetDate,
|
|
15591
15602
|
estimatedEffort: epicDoc.frontmatter.estimatedEffort,
|
|
15592
15603
|
workItems: {
|
|
@@ -15713,7 +15724,7 @@ function createReportTools(store) {
|
|
|
15713
15724
|
const epicDocs = store.list({ type: "epic" });
|
|
15714
15725
|
const features = featureDocs.filter((f) => !args.feature || f.frontmatter.id === args.feature).map((f) => {
|
|
15715
15726
|
const linkedEpics = epicDocs.filter(
|
|
15716
|
-
(e) => e.frontmatter.linkedFeature
|
|
15727
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).includes(f.frontmatter.id)
|
|
15717
15728
|
);
|
|
15718
15729
|
const byStatus = {};
|
|
15719
15730
|
for (const e of linkedEpics) {
|
|
@@ -15917,14 +15928,14 @@ function createEpicTools(store) {
|
|
|
15917
15928
|
let docs = store.list({ type: "epic", status: args.status });
|
|
15918
15929
|
if (args.linkedFeature) {
|
|
15919
15930
|
docs = docs.filter(
|
|
15920
|
-
(d) => d.frontmatter.linkedFeature
|
|
15931
|
+
(d) => normalizeLinkedFeatures(d.frontmatter.linkedFeature).includes(args.linkedFeature)
|
|
15921
15932
|
);
|
|
15922
15933
|
}
|
|
15923
15934
|
const summary = docs.map((d) => ({
|
|
15924
15935
|
id: d.frontmatter.id,
|
|
15925
15936
|
title: d.frontmatter.title,
|
|
15926
15937
|
status: d.frontmatter.status,
|
|
15927
|
-
linkedFeature: d.frontmatter.linkedFeature,
|
|
15938
|
+
linkedFeature: normalizeLinkedFeatures(d.frontmatter.linkedFeature),
|
|
15928
15939
|
owner: d.frontmatter.owner,
|
|
15929
15940
|
targetDate: d.frontmatter.targetDate,
|
|
15930
15941
|
estimatedEffort: d.frontmatter.estimatedEffort,
|
|
@@ -15965,11 +15976,11 @@ function createEpicTools(store) {
|
|
|
15965
15976
|
),
|
|
15966
15977
|
tool10(
|
|
15967
15978
|
"create_epic",
|
|
15968
|
-
"Create a new epic linked to
|
|
15979
|
+
"Create a new epic linked to one or more approved features. All linked features must exist and be approved.",
|
|
15969
15980
|
{
|
|
15970
15981
|
title: external_exports.string().describe("Epic title"),
|
|
15971
15982
|
content: external_exports.string().describe("Epic description and scope"),
|
|
15972
|
-
linkedFeature: external_exports.string().describe("Feature ID to link this epic to (e.g. 'F-001')"),
|
|
15983
|
+
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'])"),
|
|
15973
15984
|
status: external_exports.enum(["planned", "in-progress", "done"]).optional().describe("Epic status (default: 'planned')"),
|
|
15974
15985
|
owner: external_exports.string().optional().describe("Epic owner"),
|
|
15975
15986
|
targetDate: external_exports.string().optional().describe("Target completion date (ISO format)"),
|
|
@@ -15977,45 +15988,48 @@ function createEpicTools(store) {
|
|
|
15977
15988
|
tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
|
|
15978
15989
|
},
|
|
15979
15990
|
async (args) => {
|
|
15980
|
-
const
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
15998
|
-
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
15991
|
+
const linkedFeatures = normalizeLinkedFeatures(args.linkedFeature);
|
|
15992
|
+
for (const featureId of linkedFeatures) {
|
|
15993
|
+
const feature = store.get(featureId);
|
|
15994
|
+
if (!feature) {
|
|
15995
|
+
return {
|
|
15996
|
+
content: [
|
|
15997
|
+
{
|
|
15998
|
+
type: "text",
|
|
15999
|
+
text: `Feature ${featureId} not found`
|
|
16000
|
+
}
|
|
16001
|
+
],
|
|
16002
|
+
isError: true
|
|
16003
|
+
};
|
|
16004
|
+
}
|
|
16005
|
+
if (feature.frontmatter.type !== "feature") {
|
|
16006
|
+
return {
|
|
16007
|
+
content: [
|
|
16008
|
+
{
|
|
16009
|
+
type: "text",
|
|
16010
|
+
text: `${featureId} is a ${feature.frontmatter.type}, not a feature`
|
|
16011
|
+
}
|
|
16012
|
+
],
|
|
16013
|
+
isError: true
|
|
16014
|
+
};
|
|
16015
|
+
}
|
|
16016
|
+
if (feature.frontmatter.status !== "approved") {
|
|
16017
|
+
return {
|
|
16018
|
+
content: [
|
|
16019
|
+
{
|
|
16020
|
+
type: "text",
|
|
16021
|
+
text: `Feature ${featureId} has status '${feature.frontmatter.status}'. Only approved features can have epics. Ask the Product Owner to approve it first.`
|
|
16022
|
+
}
|
|
16023
|
+
],
|
|
16024
|
+
isError: true
|
|
16025
|
+
};
|
|
16026
|
+
}
|
|
16013
16027
|
}
|
|
16014
16028
|
const frontmatter = {
|
|
16015
16029
|
title: args.title,
|
|
16016
16030
|
status: args.status ?? "planned",
|
|
16017
|
-
linkedFeature:
|
|
16018
|
-
tags: [
|
|
16031
|
+
linkedFeature: linkedFeatures,
|
|
16032
|
+
tags: [...generateFeatureTags(linkedFeatures), ...args.tags ?? []]
|
|
16019
16033
|
};
|
|
16020
16034
|
if (args.owner) frontmatter.owner = args.owner;
|
|
16021
16035
|
if (args.targetDate) frontmatter.targetDate = args.targetDate;
|
|
@@ -16025,7 +16039,7 @@ function createEpicTools(store) {
|
|
|
16025
16039
|
content: [
|
|
16026
16040
|
{
|
|
16027
16041
|
type: "text",
|
|
16028
|
-
text: `Created epic ${doc.frontmatter.id}: ${doc.frontmatter.title} (linked to ${
|
|
16042
|
+
text: `Created epic ${doc.frontmatter.id}: ${doc.frontmatter.title} (linked to ${linkedFeatures.join(", ")})`
|
|
16029
16043
|
}
|
|
16030
16044
|
]
|
|
16031
16045
|
};
|
|
@@ -16033,7 +16047,7 @@ function createEpicTools(store) {
|
|
|
16033
16047
|
),
|
|
16034
16048
|
tool10(
|
|
16035
16049
|
"update_epic",
|
|
16036
|
-
"Update an existing epic
|
|
16050
|
+
"Update an existing epic, including its linked features.",
|
|
16037
16051
|
{
|
|
16038
16052
|
id: external_exports.string().describe("Epic ID to update"),
|
|
16039
16053
|
title: external_exports.string().optional().describe("New title"),
|
|
@@ -16042,10 +16056,49 @@ function createEpicTools(store) {
|
|
|
16042
16056
|
owner: external_exports.string().optional().describe("New owner"),
|
|
16043
16057
|
targetDate: external_exports.string().optional().describe("New target date"),
|
|
16044
16058
|
estimatedEffort: external_exports.string().optional().describe("New estimated effort"),
|
|
16059
|
+
linkedFeature: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]).optional().describe("New linked feature ID(s)"),
|
|
16045
16060
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
16046
16061
|
},
|
|
16047
16062
|
async (args) => {
|
|
16048
|
-
const { id, content, ...updates } = args;
|
|
16063
|
+
const { id, content, linkedFeature: rawLinkedFeature, tags: userTags, ...updates } = args;
|
|
16064
|
+
if (rawLinkedFeature !== void 0) {
|
|
16065
|
+
const linkedFeatures = normalizeLinkedFeatures(rawLinkedFeature);
|
|
16066
|
+
for (const featureId of linkedFeatures) {
|
|
16067
|
+
const feature = store.get(featureId);
|
|
16068
|
+
if (!feature) {
|
|
16069
|
+
return {
|
|
16070
|
+
content: [
|
|
16071
|
+
{ type: "text", text: `Feature ${featureId} not found` }
|
|
16072
|
+
],
|
|
16073
|
+
isError: true
|
|
16074
|
+
};
|
|
16075
|
+
}
|
|
16076
|
+
if (feature.frontmatter.type !== "feature") {
|
|
16077
|
+
return {
|
|
16078
|
+
content: [
|
|
16079
|
+
{ type: "text", text: `${featureId} is a ${feature.frontmatter.type}, not a feature` }
|
|
16080
|
+
],
|
|
16081
|
+
isError: true
|
|
16082
|
+
};
|
|
16083
|
+
}
|
|
16084
|
+
if (feature.frontmatter.status !== "approved") {
|
|
16085
|
+
return {
|
|
16086
|
+
content: [
|
|
16087
|
+
{ type: "text", text: `Feature ${featureId} has status '${feature.frontmatter.status}'. Only approved features can have epics. Ask the Product Owner to approve it first.` }
|
|
16088
|
+
],
|
|
16089
|
+
isError: true
|
|
16090
|
+
};
|
|
16091
|
+
}
|
|
16092
|
+
}
|
|
16093
|
+
updates.linkedFeature = linkedFeatures;
|
|
16094
|
+
const existingDoc = store.get(id);
|
|
16095
|
+
const existingTags = existingDoc?.frontmatter.tags ?? [];
|
|
16096
|
+
const nonFeatureTags = existingTags.filter((t) => !t.startsWith("feature:"));
|
|
16097
|
+
const baseTags = userTags ?? nonFeatureTags;
|
|
16098
|
+
updates.tags = [...generateFeatureTags(linkedFeatures), ...baseTags];
|
|
16099
|
+
} else if (userTags !== void 0) {
|
|
16100
|
+
updates.tags = userTags;
|
|
16101
|
+
}
|
|
16049
16102
|
const doc = store.update(id, updates, content);
|
|
16050
16103
|
return {
|
|
16051
16104
|
content: [
|
|
@@ -16380,7 +16433,9 @@ function createSprintPlanningTools(store) {
|
|
|
16380
16433
|
const questions = store.list({ type: "question", status: "open" });
|
|
16381
16434
|
const contributions = store.list({ type: "contribution" });
|
|
16382
16435
|
const approvedFeatures = features.filter((f) => f.frontmatter.status === "approved").sort((a, b) => priorityRank(a.frontmatter.priority) - priorityRank(b.frontmatter.priority)).map((f) => {
|
|
16383
|
-
const linkedEpics = epics.filter(
|
|
16436
|
+
const linkedEpics = epics.filter(
|
|
16437
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).includes(f.frontmatter.id)
|
|
16438
|
+
);
|
|
16384
16439
|
const epicsByStatus = {};
|
|
16385
16440
|
for (const e of linkedEpics) {
|
|
16386
16441
|
epicsByStatus[e.frontmatter.status] = (epicsByStatus[e.frontmatter.status] ?? 0) + 1;
|
|
@@ -16405,22 +16460,25 @@ function createSprintPlanningTools(store) {
|
|
|
16405
16460
|
);
|
|
16406
16461
|
if (args.focusFeature) {
|
|
16407
16462
|
backlogEpics = backlogEpics.filter(
|
|
16408
|
-
(e) => e.frontmatter.linkedFeature
|
|
16463
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).includes(args.focusFeature)
|
|
16409
16464
|
);
|
|
16410
16465
|
}
|
|
16411
16466
|
const backlog = backlogEpics.sort((a, b) => {
|
|
16412
|
-
const
|
|
16413
|
-
const
|
|
16414
|
-
|
|
16467
|
+
const aFeatures = normalizeLinkedFeatures(a.frontmatter.linkedFeature);
|
|
16468
|
+
const bFeatures = normalizeLinkedFeatures(b.frontmatter.linkedFeature);
|
|
16469
|
+
const aRank = Math.min(...aFeatures.map((id) => priorityRank(featureMap.get(id)?.frontmatter.priority)), 99);
|
|
16470
|
+
const bRank = Math.min(...bFeatures.map((id) => priorityRank(featureMap.get(id)?.frontmatter.priority)), 99);
|
|
16471
|
+
return aRank - bRank;
|
|
16415
16472
|
}).map((e) => {
|
|
16416
|
-
const
|
|
16473
|
+
const linkedFeatures = normalizeLinkedFeatures(e.frontmatter.linkedFeature);
|
|
16474
|
+
const parents = linkedFeatures.map((id) => featureMap.get(id)).filter(Boolean);
|
|
16417
16475
|
return {
|
|
16418
16476
|
id: e.frontmatter.id,
|
|
16419
16477
|
title: e.frontmatter.title,
|
|
16420
16478
|
status: e.frontmatter.status,
|
|
16421
|
-
linkedFeature:
|
|
16422
|
-
featureTitle:
|
|
16423
|
-
featurePriority:
|
|
16479
|
+
linkedFeature: linkedFeatures,
|
|
16480
|
+
featureTitle: parents.map((p) => p.frontmatter.title).join(", ") || null,
|
|
16481
|
+
featurePriority: parents.map((p) => p.frontmatter.priority).join(", ") || null,
|
|
16424
16482
|
estimatedEffort: e.frontmatter.estimatedEffort ?? null,
|
|
16425
16483
|
targetDate: e.frontmatter.targetDate ?? null
|
|
16426
16484
|
};
|
|
@@ -16501,8 +16559,8 @@ function createSprintPlanningTools(store) {
|
|
|
16501
16559
|
const epicsAtRisk = epics.filter((e) => {
|
|
16502
16560
|
if (e.frontmatter.status === "done") return false;
|
|
16503
16561
|
if (e.frontmatter.targetDate && e.frontmatter.targetDate < now) return true;
|
|
16504
|
-
const
|
|
16505
|
-
if (
|
|
16562
|
+
const linkedIds = normalizeLinkedFeatures(e.frontmatter.linkedFeature);
|
|
16563
|
+
if (linkedIds.some((id) => featureMap.get(id)?.frontmatter.status === "deferred")) return true;
|
|
16506
16564
|
return false;
|
|
16507
16565
|
}).map((e) => ({
|
|
16508
16566
|
id: e.frontmatter.id,
|
|
@@ -18421,7 +18479,7 @@ function getDiagramData(store) {
|
|
|
18421
18479
|
id: fm.id,
|
|
18422
18480
|
title: fm.title,
|
|
18423
18481
|
status: fm.status,
|
|
18424
|
-
linkedFeature: fm.linkedFeature
|
|
18482
|
+
linkedFeature: normalizeLinkedFeatures(fm.linkedFeature)
|
|
18425
18483
|
});
|
|
18426
18484
|
break;
|
|
18427
18485
|
case "feature":
|
|
@@ -19212,8 +19270,8 @@ function buildArtifactFlowchart(data) {
|
|
|
19212
19270
|
lines.push(" classDef default fill:#1e293b,stroke:#475569,color:#e2e8f0");
|
|
19213
19271
|
const nodeIds = /* @__PURE__ */ new Set();
|
|
19214
19272
|
for (const epic of data.epics) {
|
|
19215
|
-
|
|
19216
|
-
const feature = data.features.find((f) => f.id ===
|
|
19273
|
+
for (const featureId of epic.linkedFeature) {
|
|
19274
|
+
const feature = data.features.find((f) => f.id === featureId);
|
|
19217
19275
|
if (feature) {
|
|
19218
19276
|
const fNode = feature.id.replace(/-/g, "_");
|
|
19219
19277
|
const eNode = epic.id.replace(/-/g, "_");
|