mrvn-cli 0.4.7 → 0.4.9

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/marvin.js CHANGED
@@ -14573,13 +14573,14 @@ function collectSprintSummaryData(store, sprintId) {
14573
14573
  const workItemDocs = allDocs.filter(
14574
14574
  (d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.tags?.includes(sprintTag)
14575
14575
  );
14576
+ const primaryDocs = workItemDocs.filter((d) => d.frontmatter.type !== "contribution");
14576
14577
  const byStatus = {};
14577
14578
  const byType = {};
14578
14579
  let doneCount = 0;
14579
14580
  let inProgressCount = 0;
14580
14581
  let openCount = 0;
14581
14582
  let blockedCount = 0;
14582
- for (const doc of workItemDocs) {
14583
+ for (const doc of primaryDocs) {
14583
14584
  const s = doc.frontmatter.status;
14584
14585
  byStatus[s] = (byStatus[s] ?? 0) + 1;
14585
14586
  byType[doc.frontmatter.type] = (byType[doc.frontmatter.type] ?? 0) + 1;
@@ -14588,21 +14589,61 @@ function collectSprintSummaryData(store, sprintId) {
14588
14589
  else if (s === "blocked") blockedCount++;
14589
14590
  else openCount++;
14590
14591
  }
14592
+ const allItemsById = /* @__PURE__ */ new Map();
14593
+ const childrenByParent = /* @__PURE__ */ new Map();
14594
+ const sprintItemIds = new Set(workItemDocs.map((d) => d.frontmatter.id));
14595
+ for (const doc of workItemDocs) {
14596
+ const about = doc.frontmatter.aboutArtifact;
14597
+ const streamTag = (doc.frontmatter.tags ?? []).find((t) => t.startsWith("stream:"));
14598
+ const item = {
14599
+ id: doc.frontmatter.id,
14600
+ title: doc.frontmatter.title,
14601
+ type: doc.frontmatter.type,
14602
+ status: doc.frontmatter.status,
14603
+ workStream: streamTag ? streamTag.slice(7) : void 0,
14604
+ aboutArtifact: about
14605
+ };
14606
+ allItemsById.set(item.id, item);
14607
+ if (about && sprintItemIds.has(about)) {
14608
+ if (!childrenByParent.has(about)) childrenByParent.set(about, []);
14609
+ childrenByParent.get(about).push(item);
14610
+ }
14611
+ }
14612
+ const itemsWithChildren = /* @__PURE__ */ new Set();
14613
+ for (const [parentId, children] of childrenByParent) {
14614
+ const parent = allItemsById.get(parentId);
14615
+ if (parent) {
14616
+ parent.children = children;
14617
+ for (const child of children) itemsWithChildren.add(child.id);
14618
+ }
14619
+ }
14620
+ for (const item of allItemsById.values()) {
14621
+ if (item.children) {
14622
+ for (const child of item.children) {
14623
+ const grandchildren = childrenByParent.get(child.id);
14624
+ if (grandchildren) {
14625
+ child.children = grandchildren;
14626
+ for (const gc of grandchildren) itemsWithChildren.add(gc.id);
14627
+ }
14628
+ }
14629
+ }
14630
+ }
14631
+ const items = [];
14632
+ for (const doc of workItemDocs) {
14633
+ if (!itemsWithChildren.has(doc.frontmatter.id)) {
14634
+ items.push(allItemsById.get(doc.frontmatter.id));
14635
+ }
14636
+ }
14591
14637
  const workItems = {
14592
- total: workItemDocs.length,
14638
+ total: primaryDocs.length,
14593
14639
  done: doneCount,
14594
14640
  inProgress: inProgressCount,
14595
14641
  open: openCount,
14596
14642
  blocked: blockedCount,
14597
- completionPct: workItemDocs.length > 0 ? Math.round(doneCount / workItemDocs.length * 100) : 0,
14643
+ completionPct: primaryDocs.length > 0 ? Math.round(doneCount / primaryDocs.length * 100) : 0,
14598
14644
  byStatus,
14599
14645
  byType,
14600
- items: workItemDocs.map((d) => ({
14601
- id: d.frontmatter.id,
14602
- title: d.frontmatter.title,
14603
- type: d.frontmatter.type,
14604
- status: d.frontmatter.status
14605
- }))
14646
+ items
14606
14647
  };
14607
14648
  const meetings = [];
14608
14649
  if (startDate && endDate) {
@@ -14684,7 +14725,7 @@ function collectSprintSummaryData(store, sprintId) {
14684
14725
  const prev = completedSprints[0];
14685
14726
  const prevTag = `sprint:${prev.frontmatter.id}`;
14686
14727
  const prevWorkItems = allDocs.filter(
14687
- (d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.tags?.includes(prevTag)
14728
+ (d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.type !== "contribution" && d.frontmatter.tags?.includes(prevTag)
14688
14729
  );
14689
14730
  const prevDone = prevWorkItems.filter((d) => DONE_STATUSES.has(d.frontmatter.status)).length;
14690
14731
  const prevRate = prevWorkItems.length > 0 ? Math.round(prevDone / prevWorkItems.length * 100) : 0;
@@ -15577,19 +15618,22 @@ function createContributionTools(store) {
15577
15618
  content: external_exports.string().describe("Contribution content \u2014 the input from the persona"),
15578
15619
  persona: external_exports.string().describe("Persona making the contribution (e.g. 'tech-lead')"),
15579
15620
  contributionType: external_exports.string().describe("Type of contribution (e.g. 'action-result', 'risk-finding')"),
15580
- aboutArtifact: external_exports.string().optional().describe("Artifact ID this contribution relates to (e.g. 'A-001')"),
15581
- status: external_exports.string().optional().describe("Status (default: 'open')"),
15582
- tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
15621
+ aboutArtifact: external_exports.string().describe("Artifact ID this contribution relates to (e.g. 'A-001', 'T-003')"),
15622
+ status: external_exports.string().optional().describe("Status (default: 'done')"),
15623
+ tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
15624
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag.")
15583
15625
  },
15584
15626
  async (args) => {
15585
15627
  const frontmatter = {
15586
15628
  title: args.title,
15587
- status: args.status ?? "open",
15629
+ status: args.status ?? "done",
15588
15630
  persona: args.persona,
15589
15631
  contributionType: args.contributionType
15590
15632
  };
15591
- if (args.aboutArtifact) frontmatter.aboutArtifact = args.aboutArtifact;
15592
- if (args.tags) frontmatter.tags = args.tags;
15633
+ frontmatter.aboutArtifact = args.aboutArtifact;
15634
+ const tags = [...args.tags ?? []];
15635
+ if (args.workStream) tags.push(`stream:${args.workStream}`);
15636
+ if (tags.length > 0) frontmatter.tags = tags;
15593
15637
  const doc = store.create("contribution", frontmatter, args.content);
15594
15638
  return {
15595
15639
  content: [
@@ -15608,10 +15652,18 @@ function createContributionTools(store) {
15608
15652
  id: external_exports.string().describe("Contribution ID to update"),
15609
15653
  title: external_exports.string().optional().describe("New title"),
15610
15654
  status: external_exports.string().optional().describe("New status"),
15611
- content: external_exports.string().optional().describe("New content")
15655
+ content: external_exports.string().optional().describe("New content"),
15656
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag.")
15612
15657
  },
15613
15658
  async (args) => {
15614
- const { id, content, ...updates } = args;
15659
+ const { id, content, workStream, ...updates } = args;
15660
+ if (workStream !== void 0) {
15661
+ const existing = store.get(id);
15662
+ const existingTags = existing?.frontmatter.tags ?? [];
15663
+ const filtered = existingTags.filter((t) => !t.startsWith("stream:"));
15664
+ filtered.push(`stream:${workStream}`);
15665
+ updates.tags = filtered;
15666
+ }
15615
15667
  const doc = store.update(id, updates, content);
15616
15668
  return {
15617
15669
  content: [
@@ -16072,13 +16124,15 @@ function createTaskTools(store) {
16072
16124
  title: external_exports.string().describe("Task title"),
16073
16125
  content: external_exports.string().describe("Task description and implementation details"),
16074
16126
  linkedEpic: linkedEpicArray.describe("Epic ID(s) to link this task to (e.g. ['E-001'] or ['E-001', 'E-002'])"),
16127
+ aboutArtifact: external_exports.string().optional().describe("Parent artifact this task derives from (e.g. 'A-001')"),
16075
16128
  status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("Task status (default: 'backlog')"),
16076
16129
  acceptanceCriteria: external_exports.string().optional().describe("Acceptance criteria for the task"),
16077
16130
  technicalNotes: external_exports.string().optional().describe("Technical implementation notes"),
16078
16131
  estimatedPoints: external_exports.number().optional().describe("Story point estimate"),
16079
16132
  complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("Task complexity"),
16080
16133
  priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Task priority"),
16081
- tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
16134
+ tags: external_exports.array(external_exports.string()).optional().describe("Additional tags"),
16135
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag.")
16082
16136
  },
16083
16137
  async (args) => {
16084
16138
  const linkedEpics = normalizeLinkedEpics(args.linkedEpic);
@@ -16091,12 +16145,15 @@ function createTaskTools(store) {
16091
16145
  warnings.push(`Warning: ${epicId} is a ${epic.frontmatter.type}, not an epic`);
16092
16146
  }
16093
16147
  }
16148
+ const baseTags = [...generateEpicTags(linkedEpics), ...args.tags ?? []];
16149
+ if (args.workStream) baseTags.push(`stream:${args.workStream}`);
16094
16150
  const frontmatter = {
16095
16151
  title: args.title,
16096
16152
  status: args.status ?? "backlog",
16097
16153
  linkedEpic: linkedEpics,
16098
- tags: [...generateEpicTags(linkedEpics), ...args.tags ?? []]
16154
+ tags: baseTags
16099
16155
  };
16156
+ if (args.aboutArtifact) frontmatter.aboutArtifact = args.aboutArtifact;
16100
16157
  if (args.acceptanceCriteria) frontmatter.acceptanceCriteria = args.acceptanceCriteria;
16101
16158
  if (args.technicalNotes) frontmatter.technicalNotes = args.technicalNotes;
16102
16159
  if (args.estimatedPoints !== void 0) frontmatter.estimatedPoints = args.estimatedPoints;
@@ -16120,6 +16177,7 @@ function createTaskTools(store) {
16120
16177
  {
16121
16178
  id: external_exports.string().describe("Task ID to update"),
16122
16179
  title: external_exports.string().optional().describe("New title"),
16180
+ aboutArtifact: external_exports.string().optional().describe("Parent artifact this task derives from (e.g. 'A-001')"),
16123
16181
  status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("New status"),
16124
16182
  content: external_exports.string().optional().describe("New content"),
16125
16183
  linkedEpic: linkedEpicArray.optional().describe("New linked epic ID(s)"),
@@ -16128,10 +16186,11 @@ function createTaskTools(store) {
16128
16186
  estimatedPoints: external_exports.number().optional().describe("New story point estimate"),
16129
16187
  complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("New complexity"),
16130
16188
  priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
16131
- tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)")
16189
+ tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)"),
16190
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag.")
16132
16191
  },
16133
16192
  async (args) => {
16134
- const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, ...updates } = args;
16193
+ const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, workStream, ...updates } = args;
16135
16194
  const warnings = [];
16136
16195
  if (rawLinkedEpic !== void 0) {
16137
16196
  const linkedEpics = normalizeLinkedEpics(rawLinkedEpic);
@@ -16152,6 +16211,12 @@ function createTaskTools(store) {
16152
16211
  } else if (userTags !== void 0) {
16153
16212
  updates.tags = userTags;
16154
16213
  }
16214
+ if (workStream !== void 0) {
16215
+ const currentTags = updates.tags ?? store.get(id)?.frontmatter.tags ?? [];
16216
+ const filtered = currentTags.filter((t) => !t.startsWith("stream:"));
16217
+ filtered.push(`stream:${workStream}`);
16218
+ updates.tags = filtered;
16219
+ }
16155
16220
  const doc = store.update(id, updates, content);
16156
16221
  const parts = [`Updated task ${doc.frontmatter.id}: ${doc.frontmatter.title}`];
16157
16222
  if (warnings.length > 0) {
@@ -18138,7 +18203,8 @@ function createActionTools(store) {
18138
18203
  priority: external_exports.string().optional().describe("Priority (high, medium, low)"),
18139
18204
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
18140
18205
  dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
18141
- sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (e.g. ['SP-001']). Adds sprint:SP-xxx tags.")
18206
+ sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (e.g. ['SP-001']). Adds sprint:SP-xxx tags."),
18207
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag.")
18142
18208
  },
18143
18209
  async (args) => {
18144
18210
  const tags = [...args.tags ?? []];
@@ -18148,6 +18214,9 @@ function createActionTools(store) {
18148
18214
  if (!tags.includes(tag)) tags.push(tag);
18149
18215
  }
18150
18216
  }
18217
+ if (args.workStream) {
18218
+ tags.push(`stream:${args.workStream}`);
18219
+ }
18151
18220
  const doc = store.create(
18152
18221
  "action",
18153
18222
  {
@@ -18185,10 +18254,11 @@ function createActionTools(store) {
18185
18254
  priority: external_exports.string().optional().describe("New priority"),
18186
18255
  dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
18187
18256
  tags: external_exports.array(external_exports.string()).optional().describe("Replace all tags. When provided with sprints, sprint tags are merged into this array."),
18188
- sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001'].")
18257
+ sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001']."),
18258
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag.")
18189
18259
  },
18190
18260
  async (args) => {
18191
- const { id, content, sprints, tags, ...updates } = args;
18261
+ const { id, content, sprints, tags, workStream, ...updates } = args;
18192
18262
  if (tags !== void 0) {
18193
18263
  const merged = [...tags];
18194
18264
  if (sprints) {
@@ -18197,8 +18267,14 @@ function createActionTools(store) {
18197
18267
  if (!merged.includes(tag)) merged.push(tag);
18198
18268
  }
18199
18269
  }
18200
- updates.tags = merged;
18201
- } else if (sprints !== void 0) {
18270
+ if (workStream !== void 0) {
18271
+ const filtered = merged.filter((t) => !t.startsWith("stream:"));
18272
+ filtered.push(`stream:${workStream}`);
18273
+ updates.tags = filtered;
18274
+ } else {
18275
+ updates.tags = merged;
18276
+ }
18277
+ } else if (sprints !== void 0 || workStream !== void 0) {
18202
18278
  const existing = store.get(id);
18203
18279
  if (!existing) {
18204
18280
  return {
@@ -18206,10 +18282,16 @@ function createActionTools(store) {
18206
18282
  isError: true
18207
18283
  };
18208
18284
  }
18209
- const existingTags = existing.frontmatter.tags ?? [];
18210
- const nonSprintTags = existingTags.filter((t) => !t.startsWith("sprint:"));
18211
- const newSprintTags = sprints.map((s) => `sprint:${s}`);
18212
- updates.tags = [...nonSprintTags, ...newSprintTags];
18285
+ let existingTags = existing.frontmatter.tags ?? [];
18286
+ if (sprints !== void 0) {
18287
+ existingTags = existingTags.filter((t) => !t.startsWith("sprint:"));
18288
+ existingTags.push(...sprints.map((s) => `sprint:${s}`));
18289
+ }
18290
+ if (workStream !== void 0) {
18291
+ existingTags = existingTags.filter((t) => !t.startsWith("stream:"));
18292
+ existingTags.push(`stream:${workStream}`);
18293
+ }
18294
+ updates.tags = existingTags;
18213
18295
  }
18214
18296
  const doc = store.update(id, updates, content);
18215
18297
  return {
@@ -18374,14 +18456,19 @@ function createDocumentTools(store) {
18374
18456
  {
18375
18457
  type: external_exports.string().optional().describe(`Filter by document type (registered types: ${store.registeredTypes.join(", ")})`),
18376
18458
  status: external_exports.string().optional().describe("Filter by status"),
18377
- tag: external_exports.string().optional().describe("Filter by tag")
18459
+ tag: external_exports.string().optional().describe("Filter by tag"),
18460
+ workStream: external_exports.string().optional().describe("Filter by work stream name (matches stream:<value> tag)")
18378
18461
  },
18379
18462
  async (args) => {
18380
- const docs = store.list({
18463
+ let docs = store.list({
18381
18464
  type: args.type,
18382
18465
  status: args.status,
18383
18466
  tag: args.tag
18384
18467
  });
18468
+ if (args.workStream) {
18469
+ const streamTag = `stream:${args.workStream}`;
18470
+ docs = docs.filter((d) => d.frontmatter.tags?.includes(streamTag));
18471
+ }
18385
18472
  const summary = docs.map((d) => ({
18386
18473
  id: d.frontmatter.id,
18387
18474
  title: d.frontmatter.title,
@@ -19419,6 +19506,17 @@ tr:hover td {
19419
19506
  background: var(--bg-hover);
19420
19507
  }
19421
19508
 
19509
+ /* Hierarchical work-item sub-rows */
19510
+ .child-row td {
19511
+ font-size: 0.8125rem;
19512
+ border-bottom-style: dashed;
19513
+ }
19514
+ .contribution-row td {
19515
+ font-size: 0.8125rem;
19516
+ color: var(--text-dim);
19517
+ border-bottom-style: dashed;
19518
+ }
19519
+
19422
19520
  /* GAR */
19423
19521
  .gar-overall {
19424
19522
  text-align: center;
@@ -21106,22 +21204,36 @@ function sprintSummaryPage(data, cached2) {
21106
21204
  </div>`,
21107
21205
  { titleTag: "h3" }
21108
21206
  ) : "";
21109
- const workItemsSection = data.workItems.total > 0 ? collapsibleSection(
21207
+ function renderItemRows(items, depth = 0) {
21208
+ return items.flatMap((w) => {
21209
+ const isChild = depth > 0;
21210
+ const isContribution = w.type === "contribution";
21211
+ const rowClass = isContribution ? ' class="contribution-row"' : isChild ? ' class="child-row"' : "";
21212
+ const indent = depth > 0 ? ` style="padding-left: ${0.75 + depth * 1}rem"` : "";
21213
+ const streamCell = w.workStream ? `<span class="badge badge-subtle">${escapeHtml(w.workStream)}</span>` : "";
21214
+ const row = `
21215
+ <tr${rowClass}>
21216
+ <td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
21217
+ <td>${escapeHtml(w.title)}</td>
21218
+ <td>${streamCell}</td>
21219
+ <td>${escapeHtml(typeLabel(w.type))}</td>
21220
+ <td>${statusBadge(w.status)}</td>
21221
+ </tr>`;
21222
+ const childRows = w.children ? renderItemRows(w.children, depth + 1) : [];
21223
+ return [row, ...childRows];
21224
+ });
21225
+ }
21226
+ const workItemRows = renderItemRows(data.workItems.items);
21227
+ const workItemsSection = workItemRows.length > 0 ? collapsibleSection(
21110
21228
  "ss-work-items",
21111
21229
  "Work Items",
21112
21230
  `<div class="table-wrap">
21113
21231
  <table>
21114
21232
  <thead>
21115
- <tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th></tr>
21233
+ <tr><th>ID</th><th>Title</th><th>Stream</th><th>Type</th><th>Status</th></tr>
21116
21234
  </thead>
21117
21235
  <tbody>
21118
- ${data.workItems.items.map((w) => `
21119
- <tr>
21120
- <td><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
21121
- <td>${escapeHtml(w.title)}</td>
21122
- <td>${escapeHtml(typeLabel(w.type))}</td>
21123
- <td>${statusBadge(w.status)}</td>
21124
- </tr>`).join("")}
21236
+ ${workItemRows.join("")}
21125
21237
  </tbody>
21126
21238
  </table>
21127
21239
  </div>`,
@@ -26159,7 +26271,7 @@ function createProgram() {
26159
26271
  const program2 = new Command();
26160
26272
  program2.name("marvin").description(
26161
26273
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
26162
- ).version("0.4.7");
26274
+ ).version("0.4.9");
26163
26275
  program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
26164
26276
  await initCommand();
26165
26277
  });