mrvn-cli 0.4.8 → 0.4.10

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 CHANGED
@@ -14557,7 +14557,8 @@ function createActionTools(store) {
14557
14557
  priority: external_exports.string().optional().describe("Priority (high, medium, low)"),
14558
14558
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
14559
14559
  dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
14560
- sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (e.g. ['SP-001']). Adds sprint:SP-xxx tags.")
14560
+ sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (e.g. ['SP-001']). Adds sprint:SP-xxx tags."),
14561
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag.")
14561
14562
  },
14562
14563
  async (args) => {
14563
14564
  const tags = [...args.tags ?? []];
@@ -14567,6 +14568,9 @@ function createActionTools(store) {
14567
14568
  if (!tags.includes(tag)) tags.push(tag);
14568
14569
  }
14569
14570
  }
14571
+ if (args.workStream) {
14572
+ tags.push(`stream:${args.workStream}`);
14573
+ }
14570
14574
  const doc = store.create(
14571
14575
  "action",
14572
14576
  {
@@ -14604,10 +14608,11 @@ function createActionTools(store) {
14604
14608
  priority: external_exports.string().optional().describe("New priority"),
14605
14609
  dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
14606
14610
  tags: external_exports.array(external_exports.string()).optional().describe("Replace all tags. When provided with sprints, sprint tags are merged into this array."),
14607
- sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001'].")
14611
+ sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001']."),
14612
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag.")
14608
14613
  },
14609
14614
  async (args) => {
14610
- const { id, content, sprints, tags, ...updates } = args;
14615
+ const { id, content, sprints, tags, workStream, ...updates } = args;
14611
14616
  if (tags !== void 0) {
14612
14617
  const merged = [...tags];
14613
14618
  if (sprints) {
@@ -14616,8 +14621,14 @@ function createActionTools(store) {
14616
14621
  if (!merged.includes(tag)) merged.push(tag);
14617
14622
  }
14618
14623
  }
14619
- updates.tags = merged;
14620
- } else if (sprints !== void 0) {
14624
+ if (workStream !== void 0) {
14625
+ const filtered = merged.filter((t) => !t.startsWith("stream:"));
14626
+ filtered.push(`stream:${workStream}`);
14627
+ updates.tags = filtered;
14628
+ } else {
14629
+ updates.tags = merged;
14630
+ }
14631
+ } else if (sprints !== void 0 || workStream !== void 0) {
14621
14632
  const existing = store.get(id);
14622
14633
  if (!existing) {
14623
14634
  return {
@@ -14625,10 +14636,16 @@ function createActionTools(store) {
14625
14636
  isError: true
14626
14637
  };
14627
14638
  }
14628
- const existingTags = existing.frontmatter.tags ?? [];
14629
- const nonSprintTags = existingTags.filter((t) => !t.startsWith("sprint:"));
14630
- const newSprintTags = sprints.map((s) => `sprint:${s}`);
14631
- updates.tags = [...nonSprintTags, ...newSprintTags];
14639
+ let existingTags = existing.frontmatter.tags ?? [];
14640
+ if (sprints !== void 0) {
14641
+ existingTags = existingTags.filter((t) => !t.startsWith("sprint:"));
14642
+ existingTags.push(...sprints.map((s) => `sprint:${s}`));
14643
+ }
14644
+ if (workStream !== void 0) {
14645
+ existingTags = existingTags.filter((t) => !t.startsWith("stream:"));
14646
+ existingTags.push(`stream:${workStream}`);
14647
+ }
14648
+ updates.tags = existingTags;
14632
14649
  }
14633
14650
  const doc = store.update(id, updates, content);
14634
14651
  return {
@@ -14793,14 +14810,19 @@ function createDocumentTools(store) {
14793
14810
  {
14794
14811
  type: external_exports.string().optional().describe(`Filter by document type (registered types: ${store.registeredTypes.join(", ")})`),
14795
14812
  status: external_exports.string().optional().describe("Filter by status"),
14796
- tag: external_exports.string().optional().describe("Filter by tag")
14813
+ tag: external_exports.string().optional().describe("Filter by tag"),
14814
+ workStream: external_exports.string().optional().describe("Filter by work stream name (matches stream:<value> tag)")
14797
14815
  },
14798
14816
  async (args) => {
14799
- const docs = store.list({
14817
+ let docs = store.list({
14800
14818
  type: args.type,
14801
14819
  status: args.status,
14802
14820
  tag: args.tag
14803
14821
  });
14822
+ if (args.workStream) {
14823
+ const streamTag = `stream:${args.workStream}`;
14824
+ docs = docs.filter((d) => d.frontmatter.tags?.includes(streamTag));
14825
+ }
14804
14826
  const summary = docs.map((d) => ({
14805
14827
  id: d.frontmatter.id,
14806
14828
  title: d.frontmatter.title,
@@ -15470,7 +15492,7 @@ function collectSprintSummaryData(store, sprintId) {
15470
15492
  });
15471
15493
  const sprintTag = `sprint:${fm.id}`;
15472
15494
  const workItemDocs = allDocs.filter(
15473
- (d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.tags?.includes(sprintTag)
15495
+ (d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.type !== "meeting" && d.frontmatter.tags?.includes(sprintTag)
15474
15496
  );
15475
15497
  const primaryDocs = workItemDocs.filter((d) => d.frontmatter.type !== "contribution");
15476
15498
  const byStatus = {};
@@ -15493,11 +15515,13 @@ function collectSprintSummaryData(store, sprintId) {
15493
15515
  const sprintItemIds = new Set(workItemDocs.map((d) => d.frontmatter.id));
15494
15516
  for (const doc of workItemDocs) {
15495
15517
  const about = doc.frontmatter.aboutArtifact;
15518
+ const streamTag = (doc.frontmatter.tags ?? []).find((t) => t.startsWith("stream:"));
15496
15519
  const item = {
15497
15520
  id: doc.frontmatter.id,
15498
15521
  title: doc.frontmatter.title,
15499
15522
  type: doc.frontmatter.type,
15500
15523
  status: doc.frontmatter.status,
15524
+ workStream: streamTag ? streamTag.slice(7) : void 0,
15501
15525
  aboutArtifact: about
15502
15526
  };
15503
15527
  allItemsById.set(item.id, item);
@@ -16453,6 +16477,12 @@ a:hover { text-decoration: underline; }
16453
16477
  .badge-closed, .badge-resolved { background: rgba(52, 211, 153, 0.15); color: var(--green); }
16454
16478
  .badge-blocked { background: rgba(248, 113, 113, 0.15); color: var(--red); }
16455
16479
  .badge-default { background: rgba(139, 143, 164, 0.1); color: var(--text-dim); }
16480
+ .badge-subtle {
16481
+ background: rgba(139, 143, 164, 0.12);
16482
+ color: var(--text-dim);
16483
+ text-transform: none;
16484
+ font-weight: 500;
16485
+ }
16456
16486
 
16457
16487
  /* Table */
16458
16488
  .table-wrap {
@@ -17269,6 +17299,22 @@ tr:hover td {
17269
17299
  max-height: 0;
17270
17300
  opacity: 0;
17271
17301
  }
17302
+
17303
+ /* Sortable table headers */
17304
+ .sortable-th {
17305
+ cursor: pointer;
17306
+ user-select: none;
17307
+ }
17308
+ .sortable-th:hover {
17309
+ text-decoration: underline;
17310
+ color: var(--text);
17311
+ }
17312
+ .sort-arrow {
17313
+ display: inline-block;
17314
+ margin-left: 0.3rem;
17315
+ font-size: 0.65rem;
17316
+ opacity: 0.7;
17317
+ }
17272
17318
  `;
17273
17319
  }
17274
17320
 
@@ -18190,16 +18236,56 @@ function sprintSummaryPage(data, cached2) {
18190
18236
  </div>`,
18191
18237
  { titleTag: "h3" }
18192
18238
  ) : "";
18239
+ const STREAM_PALETTE = [
18240
+ "hsla(220, 30%, 22%, 0.45)",
18241
+ "hsla(160, 30%, 20%, 0.45)",
18242
+ "hsla(280, 25%, 22%, 0.45)",
18243
+ "hsla(30, 35%, 22%, 0.45)",
18244
+ "hsla(340, 25%, 22%, 0.45)",
18245
+ "hsla(190, 30%, 20%, 0.45)",
18246
+ "hsla(60, 25%, 20%, 0.45)",
18247
+ "hsla(120, 20%, 20%, 0.45)"
18248
+ ];
18249
+ function hashString(s) {
18250
+ let h = 0;
18251
+ for (let i = 0; i < s.length; i++) {
18252
+ h = (h << 5) - h + s.charCodeAt(i) | 0;
18253
+ }
18254
+ return Math.abs(h);
18255
+ }
18256
+ function collectStreams(items) {
18257
+ const streams = /* @__PURE__ */ new Set();
18258
+ for (const w of items) {
18259
+ if (w.workStream) streams.add(w.workStream);
18260
+ if (w.children) {
18261
+ for (const s of collectStreams(w.children)) streams.add(s);
18262
+ }
18263
+ }
18264
+ return streams;
18265
+ }
18266
+ const uniqueStreams = collectStreams(data.workItems.items);
18267
+ const streamColorMap = /* @__PURE__ */ new Map();
18268
+ for (const name of uniqueStreams) {
18269
+ streamColorMap.set(name, STREAM_PALETTE[hashString(name) % STREAM_PALETTE.length]);
18270
+ }
18271
+ const streamStyleRules = [...streamColorMap.entries()].map(([name, color]) => `tr[data-stream="${escapeHtml(name)}"] td { background: ${color}; }`).join("\n");
18272
+ const streamStyleBlock = streamStyleRules ? `<style>${streamStyleRules}</style>` : "";
18193
18273
  function renderItemRows(items, depth = 0) {
18194
18274
  return items.flatMap((w) => {
18195
18275
  const isChild = depth > 0;
18196
18276
  const isContribution = w.type === "contribution";
18197
- const rowClass = isContribution ? ' class="contribution-row"' : isChild ? ' class="child-row"' : "";
18277
+ const classes = [];
18278
+ if (isContribution) classes.push("contribution-row");
18279
+ else if (isChild) classes.push("child-row");
18280
+ const dataStream = w.workStream ? ` data-stream="${escapeHtml(w.workStream)}"` : "";
18281
+ const rowAttrs = classes.length > 0 ? ` class="${classes.join(" ")}"` : "";
18198
18282
  const indent = depth > 0 ? ` style="padding-left: ${0.75 + depth * 1}rem"` : "";
18283
+ const streamCell = w.workStream ? `<span class="badge badge-subtle">${escapeHtml(w.workStream)}</span>` : "";
18199
18284
  const row = `
18200
- <tr${rowClass}>
18285
+ <tr${rowAttrs}${dataStream}>
18201
18286
  <td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
18202
18287
  <td>${escapeHtml(w.title)}</td>
18288
+ <td>${streamCell}</td>
18203
18289
  <td>${escapeHtml(typeLabel(w.type))}</td>
18204
18290
  <td>${statusBadge(w.status)}</td>
18205
18291
  </tr>`;
@@ -18208,13 +18294,21 @@ function sprintSummaryPage(data, cached2) {
18208
18294
  });
18209
18295
  }
18210
18296
  const workItemRows = renderItemRows(data.workItems.items);
18297
+ const sortableHeaders = `<tr>
18298
+ <th class="sortable-th" onclick="sortWorkItems(0)">ID<span class="sort-arrow" id="sort-arrow-0"></span></th>
18299
+ <th class="sortable-th" onclick="sortWorkItems(1)">Title<span class="sort-arrow" id="sort-arrow-1"></span></th>
18300
+ <th class="sortable-th" onclick="sortWorkItems(2)">Stream<span class="sort-arrow" id="sort-arrow-2"></span></th>
18301
+ <th class="sortable-th" onclick="sortWorkItems(3)">Type<span class="sort-arrow" id="sort-arrow-3"></span></th>
18302
+ <th class="sortable-th" onclick="sortWorkItems(4)">Status<span class="sort-arrow" id="sort-arrow-4"></span></th>
18303
+ </tr>`;
18211
18304
  const workItemsSection = workItemRows.length > 0 ? collapsibleSection(
18212
18305
  "ss-work-items",
18213
18306
  "Work Items",
18214
- `<div class="table-wrap">
18215
- <table>
18307
+ `${streamStyleBlock}
18308
+ <div class="table-wrap">
18309
+ <table id="work-items-table">
18216
18310
  <thead>
18217
- <tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th></tr>
18311
+ ${sortableHeaders}
18218
18312
  </thead>
18219
18313
  <tbody>
18220
18314
  ${workItemRows.join("")}
@@ -18293,6 +18387,61 @@ function sprintSummaryPage(data, cached2) {
18293
18387
  </div>
18294
18388
 
18295
18389
  <script>
18390
+ var _sortCol = -1;
18391
+ var _sortAsc = true;
18392
+
18393
+ function sortWorkItems(col) {
18394
+ var table = document.getElementById('work-items-table');
18395
+ if (!table) return;
18396
+ var tbody = table.querySelector('tbody');
18397
+ var allRows = Array.from(tbody.querySelectorAll('tr'));
18398
+
18399
+ // Toggle direction if clicking the same column
18400
+ if (_sortCol === col) {
18401
+ _sortAsc = !_sortAsc;
18402
+ } else {
18403
+ _sortCol = col;
18404
+ _sortAsc = true;
18405
+ }
18406
+
18407
+ // Update sort arrows
18408
+ for (var i = 0; i < 5; i++) {
18409
+ var arrow = document.getElementById('sort-arrow-' + i);
18410
+ if (arrow) arrow.textContent = i === col ? (_sortAsc ? ' \\u25B2' : ' \\u25BC') : '';
18411
+ }
18412
+
18413
+ // Group rows: root rows + their child/contribution rows
18414
+ var groups = [];
18415
+ var current = null;
18416
+ for (var r = 0; r < allRows.length; r++) {
18417
+ var row = allRows[r];
18418
+ var isChild = row.classList.contains('child-row') || row.classList.contains('contribution-row');
18419
+ if (!isChild) {
18420
+ current = { root: row, children: [] };
18421
+ groups.push(current);
18422
+ } else if (current) {
18423
+ current.children.push(row);
18424
+ }
18425
+ }
18426
+
18427
+ // Sort groups by root row text content of target column
18428
+ groups.sort(function(a, b) {
18429
+ var aText = (a.root.children[col] ? a.root.children[col].textContent : '').trim().toLowerCase();
18430
+ var bText = (b.root.children[col] ? b.root.children[col].textContent : '').trim().toLowerCase();
18431
+ if (aText < bText) return _sortAsc ? -1 : 1;
18432
+ if (aText > bText) return _sortAsc ? 1 : -1;
18433
+ return 0;
18434
+ });
18435
+
18436
+ // Re-append rows in sorted order
18437
+ for (var g = 0; g < groups.length; g++) {
18438
+ tbody.appendChild(groups[g].root);
18439
+ for (var c = 0; c < groups[g].children.length; c++) {
18440
+ tbody.appendChild(groups[g].children[c]);
18441
+ }
18442
+ }
18443
+ }
18444
+
18296
18445
  async function generateSummary() {
18297
18446
  var btn = document.getElementById('generate-btn');
18298
18447
  var loading = document.getElementById('summary-loading');
@@ -19513,7 +19662,8 @@ function createContributionTools(store) {
19513
19662
  contributionType: external_exports.string().describe("Type of contribution (e.g. 'action-result', 'risk-finding')"),
19514
19663
  aboutArtifact: external_exports.string().describe("Artifact ID this contribution relates to (e.g. 'A-001', 'T-003')"),
19515
19664
  status: external_exports.string().optional().describe("Status (default: 'done')"),
19516
- tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
19665
+ tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
19666
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag.")
19517
19667
  },
19518
19668
  async (args) => {
19519
19669
  const frontmatter = {
@@ -19523,7 +19673,9 @@ function createContributionTools(store) {
19523
19673
  contributionType: args.contributionType
19524
19674
  };
19525
19675
  frontmatter.aboutArtifact = args.aboutArtifact;
19526
- if (args.tags) frontmatter.tags = args.tags;
19676
+ const tags = [...args.tags ?? []];
19677
+ if (args.workStream) tags.push(`stream:${args.workStream}`);
19678
+ if (tags.length > 0) frontmatter.tags = tags;
19527
19679
  const doc = store.create("contribution", frontmatter, args.content);
19528
19680
  return {
19529
19681
  content: [
@@ -19542,10 +19694,18 @@ function createContributionTools(store) {
19542
19694
  id: external_exports.string().describe("Contribution ID to update"),
19543
19695
  title: external_exports.string().optional().describe("New title"),
19544
19696
  status: external_exports.string().optional().describe("New status"),
19545
- content: external_exports.string().optional().describe("New content")
19697
+ content: external_exports.string().optional().describe("New content"),
19698
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag.")
19546
19699
  },
19547
19700
  async (args) => {
19548
- const { id, content, ...updates } = args;
19701
+ const { id, content, workStream, ...updates } = args;
19702
+ if (workStream !== void 0) {
19703
+ const existing = store.get(id);
19704
+ const existingTags = existing?.frontmatter.tags ?? [];
19705
+ const filtered = existingTags.filter((t) => !t.startsWith("stream:"));
19706
+ filtered.push(`stream:${workStream}`);
19707
+ updates.tags = filtered;
19708
+ }
19549
19709
  const doc = store.update(id, updates, content);
19550
19710
  return {
19551
19711
  content: [
@@ -20013,7 +20173,8 @@ function createTaskTools(store) {
20013
20173
  estimatedPoints: external_exports.number().optional().describe("Story point estimate"),
20014
20174
  complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("Task complexity"),
20015
20175
  priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Task priority"),
20016
- tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
20176
+ tags: external_exports.array(external_exports.string()).optional().describe("Additional tags"),
20177
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Adds a stream:<value> tag.")
20017
20178
  },
20018
20179
  async (args) => {
20019
20180
  const linkedEpics = normalizeLinkedEpics(args.linkedEpic);
@@ -20026,11 +20187,13 @@ function createTaskTools(store) {
20026
20187
  warnings.push(`Warning: ${epicId} is a ${epic.frontmatter.type}, not an epic`);
20027
20188
  }
20028
20189
  }
20190
+ const baseTags = [...generateEpicTags(linkedEpics), ...args.tags ?? []];
20191
+ if (args.workStream) baseTags.push(`stream:${args.workStream}`);
20029
20192
  const frontmatter = {
20030
20193
  title: args.title,
20031
20194
  status: args.status ?? "backlog",
20032
20195
  linkedEpic: linkedEpics,
20033
- tags: [...generateEpicTags(linkedEpics), ...args.tags ?? []]
20196
+ tags: baseTags
20034
20197
  };
20035
20198
  if (args.aboutArtifact) frontmatter.aboutArtifact = args.aboutArtifact;
20036
20199
  if (args.acceptanceCriteria) frontmatter.acceptanceCriteria = args.acceptanceCriteria;
@@ -20065,10 +20228,11 @@ function createTaskTools(store) {
20065
20228
  estimatedPoints: external_exports.number().optional().describe("New story point estimate"),
20066
20229
  complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("New complexity"),
20067
20230
  priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
20068
- tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)")
20231
+ tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)"),
20232
+ workStream: external_exports.string().optional().describe("Work stream name (e.g. 'Budget UX'). Replaces existing stream:<value> tag.")
20069
20233
  },
20070
20234
  async (args) => {
20071
- const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, ...updates } = args;
20235
+ const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, workStream, ...updates } = args;
20072
20236
  const warnings = [];
20073
20237
  if (rawLinkedEpic !== void 0) {
20074
20238
  const linkedEpics = normalizeLinkedEpics(rawLinkedEpic);
@@ -20089,6 +20253,12 @@ function createTaskTools(store) {
20089
20253
  } else if (userTags !== void 0) {
20090
20254
  updates.tags = userTags;
20091
20255
  }
20256
+ if (workStream !== void 0) {
20257
+ const currentTags = updates.tags ?? store.get(id)?.frontmatter.tags ?? [];
20258
+ const filtered = currentTags.filter((t) => !t.startsWith("stream:"));
20259
+ filtered.push(`stream:${workStream}`);
20260
+ updates.tags = filtered;
20261
+ }
20092
20262
  const doc = store.update(id, updates, content);
20093
20263
  const parts = [`Updated task ${doc.frontmatter.id}: ${doc.frontmatter.title}`];
20094
20264
  if (warnings.length > 0) {
@@ -26230,7 +26400,7 @@ function createProgram() {
26230
26400
  const program = new Command();
26231
26401
  program.name("marvin").description(
26232
26402
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
26233
- ).version("0.4.8");
26403
+ ).version("0.4.10");
26234
26404
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
26235
26405
  await initCommand();
26236
26406
  });