mrvn-cli 0.5.27 → 0.5.28

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
@@ -26751,10 +26751,16 @@ function resolveWeight(complexity) {
26751
26751
  function resolveProgress(frontmatter, commentAnalysisProgress) {
26752
26752
  const hasExplicitProgress = "progress" in frontmatter && typeof frontmatter.progress === "number";
26753
26753
  if (hasExplicitProgress) {
26754
- return { progress: Math.max(0, Math.min(100, Math.round(frontmatter.progress))), progressSource: "explicit" };
26754
+ return {
26755
+ progress: Math.max(0, Math.min(100, Math.round(frontmatter.progress))),
26756
+ progressSource: "explicit"
26757
+ };
26755
26758
  }
26756
26759
  if (commentAnalysisProgress !== null) {
26757
- return { progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))), progressSource: "comment-analysis" };
26760
+ return {
26761
+ progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))),
26762
+ progressSource: "comment-analysis"
26763
+ };
26758
26764
  }
26759
26765
  const status = frontmatter.status;
26760
26766
  const defaultProgress = STATUS_PROGRESS_DEFAULTS[status] ?? 0;
@@ -26779,7 +26785,13 @@ async function assessSprintProgress(store, client, host, options = {}) {
26779
26785
  sprintId: options.sprintId ?? "unknown",
26780
26786
  sprintTitle: "Sprint not found",
26781
26787
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
26782
- timeline: { startDate: null, endDate: null, daysRemaining: 0, totalDays: 0, percentComplete: 0 },
26788
+ timeline: {
26789
+ startDate: null,
26790
+ endDate: null,
26791
+ daysRemaining: 0,
26792
+ totalDays: 0,
26793
+ percentComplete: 0
26794
+ },
26783
26795
  overallProgress: 0,
26784
26796
  itemReports: [],
26785
26797
  focusAreas: [],
@@ -26787,7 +26799,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
26787
26799
  blockers: [],
26788
26800
  proposedUpdates: [],
26789
26801
  appliedUpdates: [],
26790
- errors: [`Sprint ${options.sprintId ?? "(active)"} not found. Create a sprint artifact first.`]
26802
+ errors: [
26803
+ `Sprint ${options.sprintId ?? "(active)"} not found. Create a sprint artifact first.`
26804
+ ]
26791
26805
  };
26792
26806
  }
26793
26807
  const sprintTag = `sprint:${sprintData.sprint.id}`;
@@ -26831,7 +26845,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
26831
26845
  });
26832
26846
  } else {
26833
26847
  const batchKey = batch[results.indexOf(result)];
26834
- errors.push(`Failed to fetch ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
26848
+ errors.push(
26849
+ `Failed to fetch ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
26850
+ );
26835
26851
  }
26836
26852
  }
26837
26853
  }
@@ -26873,12 +26889,16 @@ async function assessSprintProgress(store, client, host, options = {}) {
26873
26889
  }
26874
26890
  } else {
26875
26891
  const batchKey = batch[results.indexOf(result)];
26876
- errors.push(`Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
26892
+ errors.push(
26893
+ `Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
26894
+ );
26877
26895
  }
26878
26896
  }
26879
26897
  }
26880
26898
  if (queue.length > 0) {
26881
- errors.push(`Link traversal capped at ${MAX_LINKED_ISSUES} linked issues (${queue.length} remaining undiscovered)`);
26899
+ errors.push(
26900
+ `Link traversal capped at ${MAX_LINKED_ISSUES} linked issues (${queue.length} remaining undiscovered)`
26901
+ );
26882
26902
  }
26883
26903
  }
26884
26904
  const proposedUpdates = [];
@@ -26956,12 +26976,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
26956
26976
  );
26957
26977
  itemLinkedIssues = allLinks;
26958
26978
  itemLinkedIssueSignals.push(...allSignals);
26959
- analyzeLinkedIssueSignals(
26960
- allLinks,
26961
- fm,
26962
- jiraKey,
26963
- proposedUpdates
26964
- );
26979
+ analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
26965
26980
  }
26966
26981
  const report = {
26967
26982
  id: fm.id,
@@ -27080,10 +27095,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
27080
27095
  }
27081
27096
  if (options.traverseLinks) {
27082
27097
  try {
27083
- const linkedSummaries = await analyzeLinkedIssueComments(
27084
- itemReports,
27085
- linkedJiraIssues
27086
- );
27098
+ const linkedSummaries = await analyzeLinkedIssueComments(itemReports, linkedJiraIssues);
27087
27099
  for (const [artifactId, signalSummaries] of linkedSummaries) {
27088
27100
  const report = itemReports.find((r) => r.id === artifactId);
27089
27101
  if (!report) continue;
@@ -27095,7 +27107,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
27095
27107
  }
27096
27108
  }
27097
27109
  } catch (err) {
27098
- errors.push(`Linked issue comment analysis failed: ${err instanceof Error ? err.message : String(err)}`);
27110
+ errors.push(
27111
+ `Linked issue comment analysis failed: ${err instanceof Error ? err.message : String(err)}`
27112
+ );
27099
27113
  }
27100
27114
  }
27101
27115
  }
@@ -27167,9 +27181,11 @@ async function analyzeCommentsForProgress(items, jiraIssues, itemJiraKeys) {
27167
27181
  const text = extractCommentText(c.body);
27168
27182
  return ` [${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
27169
27183
  }).join("\n");
27170
- promptParts.push(`## ${item.id} \u2014 ${item.title} (${jiraKey}, Jira status: ${item.jiraStatus})
27184
+ promptParts.push(
27185
+ `## ${item.id} \u2014 ${item.title} (${jiraKey}, Jira status: ${item.jiraStatus})
27171
27186
  Comments:
27172
- ${commentTexts}`);
27187
+ ${commentTexts}`
27188
+ );
27173
27189
  }
27174
27190
  if (promptParts.length === 0) return summaries;
27175
27191
  const prompt = promptParts.join("\n\n");
@@ -27242,7 +27258,9 @@ function collectTransitiveLinks(primaryIssue, primaryIssues, linkedJiraIssues) {
27242
27258
  commentSummary: null
27243
27259
  });
27244
27260
  }
27245
- const nextLinks = collectLinkedIssues(linkedData.issue).filter((l) => l.relationship !== "subtask" && !visited.has(l.key));
27261
+ const nextLinks = collectLinkedIssues(linkedData.issue).filter(
27262
+ (l) => l.relationship !== "subtask" && !visited.has(l.key)
27263
+ );
27246
27264
  for (const next of nextLinks) {
27247
27265
  visited.add(next.key);
27248
27266
  queue.push(next);
@@ -27266,9 +27284,7 @@ function analyzeLinkedIssueSignals(linkedIssues, frontmatter, jiraKey, proposedU
27266
27284
  reason: `All blocking issues resolved: ${blockerLinks.map((l) => l.key).join(", ")}`
27267
27285
  });
27268
27286
  }
27269
- const wontDoLinks = linkedIssues.filter(
27270
- (l) => WONT_DO_STATUSES.has(l.status.toLowerCase())
27271
- );
27287
+ const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
27272
27288
  if (wontDoLinks.length > 0) {
27273
27289
  proposedUpdates.push({
27274
27290
  artifactId: frontmatter.id,
@@ -27342,7 +27358,9 @@ ${linkedParts.join("\n")}`);
27342
27358
  for (const [artifactId, linkedSummaries] of Object.entries(parsed)) {
27343
27359
  if (typeof linkedSummaries === "object" && linkedSummaries !== null) {
27344
27360
  const signalMap = /* @__PURE__ */ new Map();
27345
- for (const [key, summary] of Object.entries(linkedSummaries)) {
27361
+ for (const [key, summary] of Object.entries(
27362
+ linkedSummaries
27363
+ )) {
27346
27364
  if (typeof summary === "string") {
27347
27365
  signalMap.set(key, summary);
27348
27366
  }
@@ -27381,7 +27399,9 @@ function formatProgressReport(report) {
27381
27399
  if (report.timeline.startDate && report.timeline.endDate) {
27382
27400
  parts.push(`## Timeline`);
27383
27401
  parts.push(`${report.timeline.startDate} \u2192 ${report.timeline.endDate}`);
27384
- parts.push(`Days remaining: ${report.timeline.daysRemaining} / ${report.timeline.totalDays} (${report.timeline.percentComplete}% elapsed)`);
27402
+ parts.push(
27403
+ `Days remaining: ${report.timeline.daysRemaining} / ${report.timeline.totalDays} (${report.timeline.percentComplete}% elapsed)`
27404
+ );
27385
27405
  parts.push(`Overall progress: ${report.overallProgress}%`);
27386
27406
  parts.push("");
27387
27407
  }
@@ -27391,7 +27411,9 @@ function formatProgressReport(report) {
27391
27411
  for (const area of report.focusAreas) {
27392
27412
  const bar = progressBar6(area.progress);
27393
27413
  parts.push(`### ${area.name} ${bar} ${area.progress}%`);
27394
- parts.push(`${area.doneCount}/${area.taskCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`);
27414
+ parts.push(
27415
+ `${area.doneCount}/${area.taskCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`
27416
+ );
27395
27417
  if (area.riskWarning) {
27396
27418
  parts.push(` \u26A0 ${area.riskWarning}`);
27397
27419
  }
@@ -27430,7 +27452,9 @@ function formatProgressReport(report) {
27430
27452
  if (report.proposedUpdates.length > 0) {
27431
27453
  parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
27432
27454
  for (const update of report.proposedUpdates) {
27433
- parts.push(` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
27455
+ parts.push(
27456
+ ` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
27457
+ );
27434
27458
  parts.push(` Reason: ${update.reason}`);
27435
27459
  }
27436
27460
  parts.push("");
@@ -27440,7 +27464,9 @@ function formatProgressReport(report) {
27440
27464
  if (report.appliedUpdates.length > 0) {
27441
27465
  parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
27442
27466
  for (const update of report.appliedUpdates) {
27443
- parts.push(` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
27467
+ parts.push(
27468
+ ` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
27469
+ );
27444
27470
  }
27445
27471
  parts.push("");
27446
27472
  }
@@ -27461,7 +27487,9 @@ function formatItemLine(parts, item, depth) {
27461
27487
  const progressLabel = ` ${item.progress}%`;
27462
27488
  const weightLabel = `w${item.weight}`;
27463
27489
  const sourceLabel = item.progressSource === "explicit" ? "" : item.progressSource === "comment-analysis" ? " (llm)" : " (est)";
27464
- parts.push(`${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${sourceLabel} (${weightLabel})${jiraLabel}${driftFlag}`);
27490
+ parts.push(
27491
+ `${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${sourceLabel} (${weightLabel})${jiraLabel}${driftFlag}`
27492
+ );
27465
27493
  if (item.commentSummary) {
27466
27494
  parts.push(`${indent} \u{1F4AC} ${item.commentSummary}`);
27467
27495
  }
@@ -27471,7 +27499,9 @@ function formatItemLine(parts, item, depth) {
27471
27499
  const doneMarker = link.isDone ? " \u2713" : "";
27472
27500
  const blockerResolved = link.isDone && BLOCKER_LINK_PATTERNS.some((p) => link.relationship.toLowerCase().includes(p.split(" ")[0])) ? " unblock signal" : "";
27473
27501
  const wontDo = WONT_DO_STATUSES.has(link.status.toLowerCase()) ? " \u26A0 needs review" : "";
27474
- parts.push(`${indent} ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}${blockerResolved}${wontDo}`);
27502
+ parts.push(
27503
+ `${indent} ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}${blockerResolved}${wontDo}`
27504
+ );
27475
27505
  const signal = item.linkedIssueSignals.find((s) => s.sourceKey === link.key);
27476
27506
  if (signal?.commentSummary) {
27477
27507
  parts.push(`${indent} \u{1F4AC} ${signal.commentSummary}`);
@@ -27490,6 +27520,7 @@ function progressBar6(pct) {
27490
27520
  var MAX_ARTIFACT_NODES = 50;
27491
27521
  var MAX_LLM_DEPTH = 3;
27492
27522
  var MAX_LLM_COMMENT_CHARS = 8e3;
27523
+ var DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD = 15;
27493
27524
  async function assessArtifact(store, client, host, options) {
27494
27525
  const visited = /* @__PURE__ */ new Set();
27495
27526
  return _assessArtifactRecursive(store, client, host, options, visited, 0);
@@ -27497,10 +27528,14 @@ async function assessArtifact(store, client, host, options) {
27497
27528
  async function _assessArtifactRecursive(store, client, host, options, visited, depth) {
27498
27529
  const errors = [];
27499
27530
  if (visited.has(options.artifactId)) {
27500
- return emptyArtifactReport(options.artifactId, [`Cycle detected: ${options.artifactId} already visited`]);
27531
+ return emptyArtifactReport(options.artifactId, [
27532
+ `Cycle detected: ${options.artifactId} already visited`
27533
+ ]);
27501
27534
  }
27502
27535
  if (visited.size >= MAX_ARTIFACT_NODES) {
27503
- return emptyArtifactReport(options.artifactId, [`Node cap reached (${MAX_ARTIFACT_NODES}), skipping ${options.artifactId}`]);
27536
+ return emptyArtifactReport(options.artifactId, [
27537
+ `Node cap reached (${MAX_ARTIFACT_NODES}), skipping ${options.artifactId}`
27538
+ ]);
27504
27539
  }
27505
27540
  visited.add(options.artifactId);
27506
27541
  const doc = store.get(options.artifactId);
@@ -27570,27 +27605,29 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27570
27605
  if (result.status === "fulfilled") {
27571
27606
  const { key, issue: li, comments: lc } = result.value;
27572
27607
  linkedJiraIssues.set(key, { issue: li, comments: lc });
27573
- const newLinks = collectLinkedIssues(li).filter((l) => l.relationship !== "subtask" && !jiraVisited.has(l.key));
27608
+ const newLinks = collectLinkedIssues(li).filter(
27609
+ (l) => l.relationship !== "subtask" && !jiraVisited.has(l.key)
27610
+ );
27574
27611
  for (const nl of newLinks) {
27575
27612
  jiraVisited.add(nl.key);
27576
27613
  queue.push(nl.key);
27577
27614
  }
27578
27615
  } else {
27579
27616
  const batchKey = batch[results.indexOf(result)];
27580
- errors.push(`Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
27617
+ errors.push(
27618
+ `Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
27619
+ );
27581
27620
  }
27582
27621
  }
27583
27622
  }
27584
- const { allLinks, allSignals } = collectTransitiveLinks(
27585
- issue2,
27586
- jiraIssues,
27587
- linkedJiraIssues
27588
- );
27623
+ const { allLinks, allSignals } = collectTransitiveLinks(issue2, jiraIssues, linkedJiraIssues);
27589
27624
  linkedIssues = allLinks;
27590
27625
  linkedIssueSignals = allSignals;
27591
27626
  analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
27592
27627
  } catch (err) {
27593
- errors.push(`Failed to fetch ${jiraKey}: ${err instanceof Error ? err.message : String(err)}`);
27628
+ errors.push(
27629
+ `Failed to fetch ${jiraKey}: ${err instanceof Error ? err.message : String(err)}`
27630
+ );
27594
27631
  }
27595
27632
  }
27596
27633
  const currentProgress = getEffectiveProgress(fm);
@@ -27631,7 +27668,11 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27631
27668
  const primaryHasComments = jiraKey ? (jiraIssues.get(jiraKey)?.comments.length ?? 0) > 0 : false;
27632
27669
  let commentAnalysisProgress = null;
27633
27670
  if (depth < MAX_LLM_DEPTH && jiraKey && primaryHasComments) {
27634
- const estimatedChars = estimateCommentTextSize(jiraIssues, linkedJiraIssues, linkedIssueSignals);
27671
+ const estimatedChars = estimateCommentTextSize(
27672
+ jiraIssues,
27673
+ linkedJiraIssues,
27674
+ linkedIssueSignals
27675
+ );
27635
27676
  if (estimatedChars <= MAX_LLM_COMMENT_CHARS) {
27636
27677
  try {
27637
27678
  const analysis = await analyzeSingleArtifactComments(
@@ -27646,14 +27687,16 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27646
27687
  commentSummary = analysis.summary;
27647
27688
  commentAnalysisProgress = analysis.progressEstimate;
27648
27689
  if (commentAnalysisProgress !== null) {
27649
- const hasExplicitProgress = "progress" in fm && typeof fm.progress === "number";
27650
- if (!hasExplicitProgress && !fm.progressOverride && commentAnalysisProgress !== currentProgress) {
27690
+ const divergence = Math.abs(commentAnalysisProgress - currentProgress);
27691
+ const threshold = options.progressDivergenceThreshold ?? DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD;
27692
+ if (divergence >= threshold && commentAnalysisProgress !== currentProgress) {
27693
+ const overrideWarning = fm.progressOverride ? " \u26A0 progressOverride is set \u2014 review before applying" : "";
27651
27694
  proposedUpdates.push({
27652
27695
  artifactId: fm.id,
27653
27696
  field: "progress",
27654
27697
  currentValue: currentProgress,
27655
27698
  proposedValue: commentAnalysisProgress,
27656
- reason: `Comment analysis estimates ${commentAnalysisProgress}% progress`
27699
+ reason: `Comment-derived estimate (${commentAnalysisProgress}%) diverges from current (${currentProgress}%) by ${divergence}pp${overrideWarning}`
27657
27700
  });
27658
27701
  }
27659
27702
  }
@@ -27666,7 +27709,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27666
27709
  const children = [];
27667
27710
  for (const childId of childIds) {
27668
27711
  if (visited.size >= MAX_ARTIFACT_NODES) {
27669
- errors.push(`Node cap reached (${MAX_ARTIFACT_NODES}), ${childIds.length - children.length} children skipped`);
27712
+ errors.push(
27713
+ `Node cap reached (${MAX_ARTIFACT_NODES}), ${childIds.length - children.length} children skipped`
27714
+ );
27670
27715
  break;
27671
27716
  }
27672
27717
  const childReport = await _assessArtifactRecursive(
@@ -27710,7 +27755,10 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27710
27755
  blockerProgressValue = blockerResult.blockerProgress;
27711
27756
  totalBlockersCount = blockerResult.totalBlockers;
27712
27757
  resolvedBlockersCount = blockerResult.resolvedBlockers;
27713
- const lastProgressUpdate = findLast(proposedUpdates, (u) => u.artifactId === fm.id && u.field === "progress");
27758
+ const lastProgressUpdate = findLast(
27759
+ proposedUpdates,
27760
+ (u) => u.artifactId === fm.id && u.field === "progress"
27761
+ );
27714
27762
  const implementationProgress = lastProgressUpdate ? lastProgressUpdate.proposedValue : currentProgress;
27715
27763
  const combinedProgress = Math.round(
27716
27764
  blockerResult.blockerProgress + implementationProgress * (1 - prerequisiteWeight)
@@ -27805,7 +27853,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27805
27853
  }
27806
27854
  store.update(fm.id, payload);
27807
27855
  } catch (err) {
27808
- errors.push(`Failed to persist assessment history: ${err instanceof Error ? err.message : String(err)}`);
27856
+ errors.push(
27857
+ `Failed to persist assessment history: ${err instanceof Error ? err.message : String(err)}`
27858
+ );
27809
27859
  }
27810
27860
  }
27811
27861
  return {
@@ -27865,9 +27915,7 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
27865
27915
  signals.push(`\u{1F6AB} Blocker \u2014 "${s.snippet}"`);
27866
27916
  }
27867
27917
  }
27868
- const blockingLinks = linkedIssues.filter(
27869
- (l) => l.relationship.toLowerCase().includes("block")
27870
- );
27918
+ const blockingLinks = linkedIssues.filter((l) => l.relationship.toLowerCase().includes("block"));
27871
27919
  const activeBlockers = blockingLinks.filter((l) => !l.isDone);
27872
27920
  const resolvedBlockers = blockingLinks.filter((l) => l.isDone);
27873
27921
  if (activeBlockers.length > 0) {
@@ -27876,7 +27924,9 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
27876
27924
  }
27877
27925
  }
27878
27926
  if (resolvedBlockers.length > 0 && activeBlockers.length === 0) {
27879
- signals.push(`\u2705 Unblocked \u2014 all blocking issues resolved: ${resolvedBlockers.map((l) => l.key).join(", ")}`);
27927
+ signals.push(
27928
+ `\u2705 Unblocked \u2014 all blocking issues resolved: ${resolvedBlockers.map((l) => l.key).join(", ")}`
27929
+ );
27880
27930
  }
27881
27931
  const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
27882
27932
  for (const l of wontDoLinks) {
@@ -27938,9 +27988,11 @@ async function analyzeSingleArtifactComments(artifactId, title, jiraKey, jiraSta
27938
27988
  const text = extractCommentText(c.body);
27939
27989
  return `[${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
27940
27990
  }).join("\n");
27941
- promptParts.push(`## ${artifactId} \u2014 ${title} (${jiraKey}, status: ${jiraStatus})
27991
+ promptParts.push(
27992
+ `## ${artifactId} \u2014 ${title} (${jiraKey}, status: ${jiraStatus})
27942
27993
  Comments:
27943
- ${commentTexts}`);
27994
+ ${commentTexts}`
27995
+ );
27944
27996
  }
27945
27997
  for (const signal of linkedIssueSignals) {
27946
27998
  const linkedData = linkedJiraIssues.get(signal.sourceKey);
@@ -28029,7 +28081,7 @@ function buildAssessmentSummary(commentSummary, commentAnalysisProgress, signals
28029
28081
  if (lastProgress) return lastProgress.proposedValue;
28030
28082
  return c.marvinProgress;
28031
28083
  });
28032
- const childDoneCount = children.filter((c, i) => {
28084
+ const childDoneCount = children.filter((c) => {
28033
28085
  const updates = c.appliedUpdates.length > 0 ? c.appliedUpdates : c.proposedUpdates;
28034
28086
  const lastStatus = findLast(updates, (u) => u.field === "status");
28035
28087
  const effectiveStatus = lastStatus ? String(lastStatus.proposedValue) : c.marvinStatus;
@@ -28065,7 +28117,8 @@ function formatArtifactReport(report) {
28065
28117
  parts.push(`## Jira State (${report.jiraKey})`);
28066
28118
  const jiraParts = [`Status: ${report.jiraStatus ?? "unknown"}`];
28067
28119
  if (report.jiraAssignee) jiraParts.push(`Assignee: ${report.jiraAssignee}`);
28068
- if (report.jiraSubtaskProgress !== null) jiraParts.push(`Subtask progress: ${report.jiraSubtaskProgress}%`);
28120
+ if (report.jiraSubtaskProgress !== null)
28121
+ jiraParts.push(`Subtask progress: ${report.jiraSubtaskProgress}%`);
28069
28122
  parts.push(jiraParts.join(" | "));
28070
28123
  if (report.statusDrift) {
28071
28124
  parts.push(`\u26A0 Drift: ${report.marvinStatus} \u2192 ${report.proposedMarvinStatus}`);
@@ -28086,7 +28139,9 @@ function formatArtifactReport(report) {
28086
28139
  if (report.totalBlockers > 0) {
28087
28140
  parts.push(`## Blocker Resolution`);
28088
28141
  const bpLabel = report.blockerProgress !== null ? `${report.blockerProgress}%` : "n/a (skipped)";
28089
- parts.push(` ${report.resolvedBlockers}/${report.totalBlockers} blockers resolved \u2192 ${bpLabel} prerequisite progress`);
28142
+ parts.push(
28143
+ ` ${report.resolvedBlockers}/${report.totalBlockers} blockers resolved \u2192 ${bpLabel} prerequisite progress`
28144
+ );
28090
28145
  parts.push("");
28091
28146
  }
28092
28147
  if (report.children.length > 0) {
@@ -28095,7 +28150,9 @@ function formatArtifactReport(report) {
28095
28150
  report.children.reduce((s, c) => s + c.marvinProgress, 0) / report.children.length
28096
28151
  );
28097
28152
  const bar = progressBar6(childProgress);
28098
- parts.push(`## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`);
28153
+ parts.push(
28154
+ `## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`
28155
+ );
28099
28156
  for (const child of report.children) {
28100
28157
  formatArtifactChild(parts, child, 1);
28101
28158
  }
@@ -28105,7 +28162,9 @@ function formatArtifactReport(report) {
28105
28162
  parts.push(`## Linked Issues (${report.linkedIssues.length})`);
28106
28163
  for (const link of report.linkedIssues) {
28107
28164
  const doneMarker = link.isDone ? " \u2713" : "";
28108
- parts.push(` ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}`);
28165
+ parts.push(
28166
+ ` ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}`
28167
+ );
28109
28168
  const signal = report.linkedIssueSignals.find((s) => s.sourceKey === link.key);
28110
28169
  if (signal?.commentSummary) {
28111
28170
  parts.push(` \u{1F4AC} ${signal.commentSummary}`);
@@ -28123,7 +28182,9 @@ function formatArtifactReport(report) {
28123
28182
  if (report.proposedUpdates.length > 0) {
28124
28183
  parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
28125
28184
  for (const update of report.proposedUpdates) {
28126
- parts.push(` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
28185
+ parts.push(
28186
+ ` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
28187
+ );
28127
28188
  parts.push(` Reason: ${update.reason}`);
28128
28189
  }
28129
28190
  parts.push("");
@@ -28133,7 +28194,9 @@ function formatArtifactReport(report) {
28133
28194
  if (report.appliedUpdates.length > 0) {
28134
28195
  parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
28135
28196
  for (const update of report.appliedUpdates) {
28136
- parts.push(` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
28197
+ parts.push(
28198
+ ` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
28199
+ );
28137
28200
  }
28138
28201
  parts.push("");
28139
28202
  }
@@ -28156,7 +28219,9 @@ function formatArtifactChild(parts, child, depth) {
28156
28219
  if (s.startsWith("\u2705 No active")) continue;
28157
28220
  signalHints.push(s);
28158
28221
  }
28159
- parts.push(`${indent}${icon} ${child.artifactId} \u2014 ${child.title} [${child.marvinStatus}] ${child.marvinProgress}%${jiraLabel}${driftLabel}`);
28222
+ parts.push(
28223
+ `${indent}${icon} ${child.artifactId} \u2014 ${child.title} [${child.marvinStatus}] ${child.marvinProgress}%${jiraLabel}${driftLabel}`
28224
+ );
28160
28225
  if (child.commentSummary) {
28161
28226
  parts.push(`${indent} \u{1F4AC} ${child.commentSummary}`);
28162
28227
  }
@@ -28991,7 +29056,8 @@ function createJiraTools(store, projectConfig) {
28991
29056
  {
28992
29057
  artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'T-063', 'A-151', 'E-003')"),
28993
29058
  applyUpdates: external_exports.boolean().optional().describe("Apply proposed status/progress updates to the artifact (default false)"),
28994
- prerequisiteWeight: external_exports.number().min(0).max(1).optional().describe("Weight for blocker-resolution progress signal (0-1, default 0.3). Portion of effort attributed to dependency readiness.")
29059
+ prerequisiteWeight: external_exports.number().min(0).max(1).optional().describe("Weight for blocker-resolution progress signal (0-1, default 0.3). Portion of effort attributed to dependency readiness."),
29060
+ progressDivergenceThreshold: external_exports.number().min(0).max(100).optional().describe("Minimum divergence in percentage points between comment-derived progress estimate and stored progress to trigger a proposal (default 15).")
28995
29061
  },
28996
29062
  async (args) => {
28997
29063
  const jira = createJiraClient(jiraUserConfig);
@@ -29004,6 +29070,7 @@ function createJiraTools(store, projectConfig) {
29004
29070
  artifactId: args.artifactId,
29005
29071
  applyUpdates: args.applyUpdates ?? false,
29006
29072
  prerequisiteWeight: args.prerequisiteWeight,
29073
+ progressDivergenceThreshold: args.progressDivergenceThreshold,
29007
29074
  statusMap
29008
29075
  }
29009
29076
  );
@@ -34418,7 +34485,7 @@ function createProgram() {
34418
34485
  const program2 = new Command();
34419
34486
  program2.name("marvin").description(
34420
34487
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
34421
- ).version("0.5.27");
34488
+ ).version("0.5.28");
34422
34489
  program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
34423
34490
  await initCommand();
34424
34491
  });