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.
@@ -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 === f.frontmatter.id
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 === args.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 an approved feature. The linked feature must exist and be approved.",
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 feature = store.get(args.linkedFeature);
15981
- if (!feature) {
15982
- return {
15983
- content: [
15984
- {
15985
- type: "text",
15986
- text: `Feature ${args.linkedFeature} not found`
15987
- }
15988
- ],
15989
- isError: true
15990
- };
15991
- }
15992
- if (feature.frontmatter.type !== "feature") {
15993
- return {
15994
- content: [
15995
- {
15996
- type: "text",
15997
- text: `${args.linkedFeature} is a ${feature.frontmatter.type}, not a feature`
15998
- }
15999
- ],
16000
- isError: true
16001
- };
16002
- }
16003
- if (feature.frontmatter.status !== "approved") {
16004
- return {
16005
- content: [
16006
- {
16007
- type: "text",
16008
- text: `Feature ${args.linkedFeature} has status '${feature.frontmatter.status}'. Only approved features can have epics. Ask the Product Owner to approve it first.`
16009
- }
16010
- ],
16011
- isError: true
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: args.linkedFeature,
16018
- tags: [`feature:${args.linkedFeature}`, ...args.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 ${args.linkedFeature})`
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. The linked feature cannot be changed.",
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((e) => e.frontmatter.linkedFeature === f.frontmatter.id);
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 === args.focusFeature
16463
+ (e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).includes(args.focusFeature)
16409
16464
  );
16410
16465
  }
16411
16466
  const backlog = backlogEpics.sort((a, b) => {
16412
- const fa = featureMap.get(a.frontmatter.linkedFeature);
16413
- const fb = featureMap.get(b.frontmatter.linkedFeature);
16414
- return priorityRank(fa?.frontmatter.priority) - priorityRank(fb?.frontmatter.priority);
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 parent = featureMap.get(e.frontmatter.linkedFeature);
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: e.frontmatter.linkedFeature,
16422
- featureTitle: parent?.frontmatter.title ?? null,
16423
- featurePriority: parent?.frontmatter.priority ?? null,
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 parent = featureMap.get(e.frontmatter.linkedFeature);
16505
- if (parent?.frontmatter.status === "deferred") return true;
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
- if (epic.linkedFeature) {
19216
- const feature = data.features.find((f) => f.id === epic.linkedFeature);
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, "_");