mrvn-cli 0.5.24 → 0.5.25

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
@@ -19522,8 +19522,30 @@ function inline(text) {
19522
19522
  s = s.replace(/__([^_]+)__/g, "<strong>$1</strong>");
19523
19523
  s = s.replace(/\*([^*]+)\*/g, "<em>$1</em>");
19524
19524
  s = s.replace(/_([^_]+)_/g, "<em>$1</em>");
19525
+ s = linkArtifactIds(s);
19525
19526
  return s;
19526
19527
  }
19528
+ var ID_PREFIX_TO_TYPE = (() => {
19529
+ const entries = [];
19530
+ for (const [type, prefix] of Object.entries(CORE_ID_PREFIXES)) {
19531
+ entries.push([prefix, type]);
19532
+ }
19533
+ for (const reg of COMMON_REGISTRATIONS) {
19534
+ if (!entries.some(([p]) => p === reg.idPrefix)) {
19535
+ entries.push([reg.idPrefix, reg.type]);
19536
+ }
19537
+ }
19538
+ entries.sort((a, b) => b[0].length - a[0].length);
19539
+ return new Map(entries);
19540
+ })();
19541
+ function linkArtifactIds(html) {
19542
+ return html.replace(/\b([A-Z]{1,3})-(\d{3,})\b/g, (match, prefix, num) => {
19543
+ const type = ID_PREFIX_TO_TYPE.get(prefix);
19544
+ if (!type) return match;
19545
+ const id = `${prefix}-${num}`;
19546
+ return `<a href="/docs/${type}/${id}" class="artifact-link">${match}</a>`;
19547
+ });
19548
+ }
19527
19549
  function layout(opts, body) {
19528
19550
  const switcherHtml = opts.personaSwitcherHtml ?? "";
19529
19551
  let navHtml;
@@ -20235,6 +20257,75 @@ tr:hover td {
20235
20257
  margin: 0.75rem 0;
20236
20258
  }
20237
20259
 
20260
+ /* Artifact cross-links */
20261
+ a.artifact-link {
20262
+ color: var(--accent);
20263
+ text-decoration: none;
20264
+ font-weight: 500;
20265
+ border-bottom: 1px dotted var(--accent);
20266
+ }
20267
+ a.artifact-link:hover {
20268
+ border-bottom-style: solid;
20269
+ }
20270
+
20271
+ /* Assessment timeline */
20272
+ .assessment-timeline {
20273
+ margin-top: 1.5rem;
20274
+ }
20275
+ .assessment-timeline h3 {
20276
+ font-size: 1rem;
20277
+ font-weight: 600;
20278
+ margin-bottom: 0.75rem;
20279
+ }
20280
+ .assessment-entry {
20281
+ background: var(--bg-card);
20282
+ border: 1px solid var(--border);
20283
+ border-radius: var(--radius);
20284
+ padding: 0.75rem 1rem;
20285
+ margin-bottom: 0.75rem;
20286
+ }
20287
+ .assessment-entry.assessment-latest {
20288
+ border-left: 3px solid var(--accent);
20289
+ }
20290
+ .assessment-header {
20291
+ display: flex;
20292
+ align-items: center;
20293
+ gap: 0.5rem;
20294
+ margin-bottom: 0.5rem;
20295
+ }
20296
+ .assessment-date {
20297
+ font-size: 0.8rem;
20298
+ color: var(--text-dim);
20299
+ font-family: var(--mono);
20300
+ }
20301
+ .assessment-comment {
20302
+ font-size: 0.875rem;
20303
+ line-height: 1.6;
20304
+ margin-bottom: 0.5rem;
20305
+ }
20306
+ .assessment-stat {
20307
+ font-size: 0.8rem;
20308
+ color: var(--text-dim);
20309
+ margin-bottom: 0.25rem;
20310
+ }
20311
+ .assessment-stat strong {
20312
+ color: var(--text);
20313
+ }
20314
+ .assessment-signals {
20315
+ list-style: none;
20316
+ padding: 0;
20317
+ margin: 0.5rem 0 0;
20318
+ }
20319
+ .assessment-signals li {
20320
+ font-size: 0.8rem;
20321
+ padding: 0.15rem 0;
20322
+ }
20323
+ .progress-bar-inline {
20324
+ font-family: var(--mono);
20325
+ font-size: 0.75rem;
20326
+ letter-spacing: -0.5px;
20327
+ }
20328
+
20238
20329
  /* Filters */
20239
20330
  .filters {
20240
20331
  display: flex;
@@ -21399,23 +21490,33 @@ function documentsPage(data) {
21399
21490
  function documentDetailPage(doc) {
21400
21491
  const fm = doc.frontmatter;
21401
21492
  const label = typeLabel(fm.type);
21402
- const skipKeys = /* @__PURE__ */ new Set(["title", "type"]);
21493
+ const skipKeys = /* @__PURE__ */ new Set(["title", "type", "assessmentHistory", "assessmentSummary"]);
21403
21494
  const entries = Object.entries(fm).filter(
21404
- ([key]) => !skipKeys.has(key) && fm[key] != null
21495
+ ([key, value]) => !skipKeys.has(key) && value != null && typeof value !== "object"
21405
21496
  );
21406
- const dtDd = entries.map(([key, value]) => {
21497
+ const arrayEntries = Object.entries(fm).filter(
21498
+ ([key, value]) => !skipKeys.has(key) && Array.isArray(value) && value.every((v) => typeof v === "string")
21499
+ );
21500
+ const allEntries = [
21501
+ ...entries.filter(([, v]) => !Array.isArray(v)),
21502
+ ...arrayEntries
21503
+ ];
21504
+ const dtDd = allEntries.map(([key, value]) => {
21407
21505
  let rendered;
21408
21506
  if (key === "status") {
21409
21507
  rendered = statusBadge(value);
21410
21508
  } else if (key === "tags" && Array.isArray(value)) {
21411
21509
  rendered = value.map((t) => `<span class="badge badge-default">${escapeHtml(t)}</span>`).join(" ");
21412
- } else if (key === "created" || key === "updated") {
21510
+ } else if (key === "created" || key === "updated" || key === "lastAssessedAt" || key === "lastJiraSyncAt") {
21413
21511
  rendered = formatDate(value);
21414
21512
  } else {
21415
- rendered = escapeHtml(String(value));
21513
+ rendered = linkArtifactIds(escapeHtml(String(value)));
21416
21514
  }
21417
21515
  return `<dt>${escapeHtml(key)}</dt><dd>${rendered}</dd>`;
21418
21516
  }).join("\n ");
21517
+ const rawHistory = Array.isArray(fm.assessmentHistory) ? fm.assessmentHistory : fm.assessmentSummary && typeof fm.assessmentSummary === "object" ? [fm.assessmentSummary] : [];
21518
+ const assessmentHistory = rawHistory.filter(isValidAssessmentEntry).sort((a, b) => (b.generatedAt ?? "").localeCompare(a.generatedAt ?? ""));
21519
+ const timelineHtml = assessmentHistory.length > 0 ? renderAssessmentTimeline(assessmentHistory) : "";
21419
21520
  return `
21420
21521
  <div class="breadcrumb">
21421
21522
  <a href="/">Overview</a><span class="sep">/</span>
@@ -21435,8 +21536,73 @@ function documentDetailPage(doc) {
21435
21536
  </div>
21436
21537
 
21437
21538
  ${doc.content.trim() ? `<div class="detail-content">${renderMarkdown(doc.content)}</div>` : ""}
21539
+
21540
+ ${timelineHtml}
21438
21541
  `;
21439
21542
  }
21543
+ function isValidAssessmentEntry(value) {
21544
+ if (typeof value !== "object" || value === null) return false;
21545
+ const obj = value;
21546
+ if (typeof obj.generatedAt !== "string") return false;
21547
+ if (obj.signals !== void 0 && !Array.isArray(obj.signals)) return false;
21548
+ return true;
21549
+ }
21550
+ function normalizeEntry(entry) {
21551
+ return {
21552
+ generatedAt: entry.generatedAt ?? "",
21553
+ commentSummary: typeof entry.commentSummary === "string" ? entry.commentSummary : null,
21554
+ commentAnalysisProgress: typeof entry.commentAnalysisProgress === "number" ? entry.commentAnalysisProgress : null,
21555
+ signals: Array.isArray(entry.signals) ? entry.signals.filter((s) => typeof s === "string") : [],
21556
+ childCount: typeof entry.childCount === "number" ? entry.childCount : 0,
21557
+ childDoneCount: typeof entry.childDoneCount === "number" ? entry.childDoneCount : 0,
21558
+ childRollupProgress: typeof entry.childRollupProgress === "number" ? entry.childRollupProgress : null,
21559
+ linkedIssueCount: typeof entry.linkedIssueCount === "number" ? entry.linkedIssueCount : 0
21560
+ };
21561
+ }
21562
+ function renderAssessmentTimeline(history) {
21563
+ const entries = history.map((raw, i) => {
21564
+ const entry = normalizeEntry(raw);
21565
+ const date5 = entry.generatedAt ? formatDate(entry.generatedAt) : "Unknown date";
21566
+ const time3 = entry.generatedAt?.slice(11, 16) ?? "";
21567
+ const isLatest = i === 0;
21568
+ const parts = [];
21569
+ if (entry.commentSummary) {
21570
+ parts.push(`<div class="assessment-comment">${linkArtifactIds(escapeHtml(entry.commentSummary))}</div>`);
21571
+ }
21572
+ if (entry.commentAnalysisProgress !== null) {
21573
+ parts.push(`<div class="assessment-stat">\u{1F4CA} Comment-derived progress: <strong>${entry.commentAnalysisProgress}%</strong></div>`);
21574
+ }
21575
+ if (entry.childCount > 0) {
21576
+ const bar = progressBarHtml(entry.childRollupProgress ?? 0);
21577
+ parts.push(`<div class="assessment-stat">\u{1F476} Children: ${entry.childDoneCount}/${entry.childCount} done ${bar} ${entry.childRollupProgress ?? 0}%</div>`);
21578
+ }
21579
+ if (entry.linkedIssueCount > 0) {
21580
+ parts.push(`<div class="assessment-stat">\u{1F517} Linked issues: ${entry.linkedIssueCount}</div>`);
21581
+ }
21582
+ if (entry.signals.length > 0) {
21583
+ const signalItems = entry.signals.map((s) => `<li>${linkArtifactIds(escapeHtml(s))}</li>`).join("");
21584
+ parts.push(`<ul class="assessment-signals">${signalItems}</ul>`);
21585
+ }
21586
+ return `
21587
+ <div class="assessment-entry${isLatest ? " assessment-latest" : ""}">
21588
+ <div class="assessment-header">
21589
+ <span class="assessment-date">${escapeHtml(date5)} ${escapeHtml(time3)}</span>
21590
+ ${isLatest ? '<span class="badge badge-default">Latest</span>' : ""}
21591
+ </div>
21592
+ ${parts.join("\n")}
21593
+ </div>`;
21594
+ });
21595
+ return `
21596
+ <div class="assessment-timeline">
21597
+ <h3>Assessment History</h3>
21598
+ ${entries.join("\n")}
21599
+ </div>`;
21600
+ }
21601
+ function progressBarHtml(pct) {
21602
+ const filled = Math.round(Math.max(0, Math.min(100, pct)) / 10);
21603
+ const empty = 10 - filled;
21604
+ return `<span class="progress-bar-inline">${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}</span>`;
21605
+ }
21440
21606
 
21441
21607
  // src/web/persona-views.ts
21442
21608
  var VIEWS = /* @__PURE__ */ new Map();
@@ -27052,20 +27218,38 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27052
27218
  }
27053
27219
  }
27054
27220
  if (options.applyUpdates) {
27055
- const assessmentSummary = buildAssessmentSummary(
27221
+ const newEntry = buildAssessmentSummary(
27056
27222
  commentSummary,
27057
27223
  commentAnalysisProgress,
27058
27224
  signals,
27059
27225
  children,
27060
27226
  linkedIssues
27061
27227
  );
27228
+ const existingHistory = Array.isArray(fm.assessmentHistory) ? fm.assessmentHistory : [];
27229
+ const legacySummary = fm.assessmentSummary;
27230
+ const allEntries = [newEntry, ...existingHistory];
27231
+ if (legacySummary?.generatedAt) {
27232
+ allEntries.push(legacySummary);
27233
+ }
27234
+ const MAX_HISTORY = 100;
27235
+ const seen = /* @__PURE__ */ new Set();
27236
+ const assessmentHistory = allEntries.filter((entry) => {
27237
+ if (!entry.generatedAt) return false;
27238
+ if (seen.has(entry.generatedAt)) return false;
27239
+ seen.add(entry.generatedAt);
27240
+ return true;
27241
+ }).sort((a, b) => (b.generatedAt ?? "").localeCompare(a.generatedAt ?? "")).slice(0, MAX_HISTORY);
27062
27242
  try {
27063
- store.update(fm.id, {
27064
- assessmentSummary,
27065
- lastAssessedAt: assessmentSummary.generatedAt
27066
- });
27243
+ const payload = {
27244
+ assessmentHistory,
27245
+ lastAssessedAt: newEntry.generatedAt
27246
+ };
27247
+ if (fm.assessmentSummary !== void 0) {
27248
+ payload.assessmentSummary = void 0;
27249
+ }
27250
+ store.update(fm.id, payload);
27067
27251
  } catch (err) {
27068
- errors.push(`Failed to persist assessment summary: ${err instanceof Error ? err.message : String(err)}`);
27252
+ errors.push(`Failed to persist assessment history: ${err instanceof Error ? err.message : String(err)}`);
27069
27253
  }
27070
27254
  }
27071
27255
  return {
@@ -33661,7 +33845,7 @@ function createProgram() {
33661
33845
  const program2 = new Command();
33662
33846
  program2.name("marvin").description(
33663
33847
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
33664
- ).version("0.5.24");
33848
+ ).version("0.5.25");
33665
33849
  program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
33666
33850
  await initCommand();
33667
33851
  });