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/index.js CHANGED
@@ -26507,10 +26507,16 @@ function resolveWeight(complexity) {
26507
26507
  function resolveProgress(frontmatter, commentAnalysisProgress) {
26508
26508
  const hasExplicitProgress = "progress" in frontmatter && typeof frontmatter.progress === "number";
26509
26509
  if (hasExplicitProgress) {
26510
- return { progress: Math.max(0, Math.min(100, Math.round(frontmatter.progress))), progressSource: "explicit" };
26510
+ return {
26511
+ progress: Math.max(0, Math.min(100, Math.round(frontmatter.progress))),
26512
+ progressSource: "explicit"
26513
+ };
26511
26514
  }
26512
26515
  if (commentAnalysisProgress !== null) {
26513
- return { progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))), progressSource: "comment-analysis" };
26516
+ return {
26517
+ progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))),
26518
+ progressSource: "comment-analysis"
26519
+ };
26514
26520
  }
26515
26521
  const status = frontmatter.status;
26516
26522
  const defaultProgress = STATUS_PROGRESS_DEFAULTS[status] ?? 0;
@@ -26535,7 +26541,13 @@ async function assessSprintProgress(store, client, host, options = {}) {
26535
26541
  sprintId: options.sprintId ?? "unknown",
26536
26542
  sprintTitle: "Sprint not found",
26537
26543
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
26538
- timeline: { startDate: null, endDate: null, daysRemaining: 0, totalDays: 0, percentComplete: 0 },
26544
+ timeline: {
26545
+ startDate: null,
26546
+ endDate: null,
26547
+ daysRemaining: 0,
26548
+ totalDays: 0,
26549
+ percentComplete: 0
26550
+ },
26539
26551
  overallProgress: 0,
26540
26552
  itemReports: [],
26541
26553
  focusAreas: [],
@@ -26543,7 +26555,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
26543
26555
  blockers: [],
26544
26556
  proposedUpdates: [],
26545
26557
  appliedUpdates: [],
26546
- errors: [`Sprint ${options.sprintId ?? "(active)"} not found. Create a sprint artifact first.`]
26558
+ errors: [
26559
+ `Sprint ${options.sprintId ?? "(active)"} not found. Create a sprint artifact first.`
26560
+ ]
26547
26561
  };
26548
26562
  }
26549
26563
  const sprintTag = `sprint:${sprintData.sprint.id}`;
@@ -26587,7 +26601,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
26587
26601
  });
26588
26602
  } else {
26589
26603
  const batchKey = batch[results.indexOf(result)];
26590
- errors.push(`Failed to fetch ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
26604
+ errors.push(
26605
+ `Failed to fetch ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
26606
+ );
26591
26607
  }
26592
26608
  }
26593
26609
  }
@@ -26629,12 +26645,16 @@ async function assessSprintProgress(store, client, host, options = {}) {
26629
26645
  }
26630
26646
  } else {
26631
26647
  const batchKey = batch[results.indexOf(result)];
26632
- errors.push(`Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
26648
+ errors.push(
26649
+ `Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
26650
+ );
26633
26651
  }
26634
26652
  }
26635
26653
  }
26636
26654
  if (queue.length > 0) {
26637
- errors.push(`Link traversal capped at ${MAX_LINKED_ISSUES} linked issues (${queue.length} remaining undiscovered)`);
26655
+ errors.push(
26656
+ `Link traversal capped at ${MAX_LINKED_ISSUES} linked issues (${queue.length} remaining undiscovered)`
26657
+ );
26638
26658
  }
26639
26659
  }
26640
26660
  const proposedUpdates = [];
@@ -26712,12 +26732,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
26712
26732
  );
26713
26733
  itemLinkedIssues = allLinks;
26714
26734
  itemLinkedIssueSignals.push(...allSignals);
26715
- analyzeLinkedIssueSignals(
26716
- allLinks,
26717
- fm,
26718
- jiraKey,
26719
- proposedUpdates
26720
- );
26735
+ analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
26721
26736
  }
26722
26737
  const report = {
26723
26738
  id: fm.id,
@@ -26836,10 +26851,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
26836
26851
  }
26837
26852
  if (options.traverseLinks) {
26838
26853
  try {
26839
- const linkedSummaries = await analyzeLinkedIssueComments(
26840
- itemReports,
26841
- linkedJiraIssues
26842
- );
26854
+ const linkedSummaries = await analyzeLinkedIssueComments(itemReports, linkedJiraIssues);
26843
26855
  for (const [artifactId, signalSummaries] of linkedSummaries) {
26844
26856
  const report = itemReports.find((r) => r.id === artifactId);
26845
26857
  if (!report) continue;
@@ -26851,7 +26863,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
26851
26863
  }
26852
26864
  }
26853
26865
  } catch (err) {
26854
- errors.push(`Linked issue comment analysis failed: ${err instanceof Error ? err.message : String(err)}`);
26866
+ errors.push(
26867
+ `Linked issue comment analysis failed: ${err instanceof Error ? err.message : String(err)}`
26868
+ );
26855
26869
  }
26856
26870
  }
26857
26871
  }
@@ -26923,9 +26937,11 @@ async function analyzeCommentsForProgress(items, jiraIssues, itemJiraKeys) {
26923
26937
  const text = extractCommentText(c.body);
26924
26938
  return ` [${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
26925
26939
  }).join("\n");
26926
- promptParts.push(`## ${item.id} \u2014 ${item.title} (${jiraKey}, Jira status: ${item.jiraStatus})
26940
+ promptParts.push(
26941
+ `## ${item.id} \u2014 ${item.title} (${jiraKey}, Jira status: ${item.jiraStatus})
26927
26942
  Comments:
26928
- ${commentTexts}`);
26943
+ ${commentTexts}`
26944
+ );
26929
26945
  }
26930
26946
  if (promptParts.length === 0) return summaries;
26931
26947
  const prompt = promptParts.join("\n\n");
@@ -26998,7 +27014,9 @@ function collectTransitiveLinks(primaryIssue, primaryIssues, linkedJiraIssues) {
26998
27014
  commentSummary: null
26999
27015
  });
27000
27016
  }
27001
- const nextLinks = collectLinkedIssues(linkedData.issue).filter((l) => l.relationship !== "subtask" && !visited.has(l.key));
27017
+ const nextLinks = collectLinkedIssues(linkedData.issue).filter(
27018
+ (l) => l.relationship !== "subtask" && !visited.has(l.key)
27019
+ );
27002
27020
  for (const next of nextLinks) {
27003
27021
  visited.add(next.key);
27004
27022
  queue.push(next);
@@ -27022,9 +27040,7 @@ function analyzeLinkedIssueSignals(linkedIssues, frontmatter, jiraKey, proposedU
27022
27040
  reason: `All blocking issues resolved: ${blockerLinks.map((l) => l.key).join(", ")}`
27023
27041
  });
27024
27042
  }
27025
- const wontDoLinks = linkedIssues.filter(
27026
- (l) => WONT_DO_STATUSES.has(l.status.toLowerCase())
27027
- );
27043
+ const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
27028
27044
  if (wontDoLinks.length > 0) {
27029
27045
  proposedUpdates.push({
27030
27046
  artifactId: frontmatter.id,
@@ -27098,7 +27114,9 @@ ${linkedParts.join("\n")}`);
27098
27114
  for (const [artifactId, linkedSummaries] of Object.entries(parsed)) {
27099
27115
  if (typeof linkedSummaries === "object" && linkedSummaries !== null) {
27100
27116
  const signalMap = /* @__PURE__ */ new Map();
27101
- for (const [key, summary] of Object.entries(linkedSummaries)) {
27117
+ for (const [key, summary] of Object.entries(
27118
+ linkedSummaries
27119
+ )) {
27102
27120
  if (typeof summary === "string") {
27103
27121
  signalMap.set(key, summary);
27104
27122
  }
@@ -27137,7 +27155,9 @@ function formatProgressReport(report) {
27137
27155
  if (report.timeline.startDate && report.timeline.endDate) {
27138
27156
  parts.push(`## Timeline`);
27139
27157
  parts.push(`${report.timeline.startDate} \u2192 ${report.timeline.endDate}`);
27140
- parts.push(`Days remaining: ${report.timeline.daysRemaining} / ${report.timeline.totalDays} (${report.timeline.percentComplete}% elapsed)`);
27158
+ parts.push(
27159
+ `Days remaining: ${report.timeline.daysRemaining} / ${report.timeline.totalDays} (${report.timeline.percentComplete}% elapsed)`
27160
+ );
27141
27161
  parts.push(`Overall progress: ${report.overallProgress}%`);
27142
27162
  parts.push("");
27143
27163
  }
@@ -27147,7 +27167,9 @@ function formatProgressReport(report) {
27147
27167
  for (const area of report.focusAreas) {
27148
27168
  const bar = progressBar6(area.progress);
27149
27169
  parts.push(`### ${area.name} ${bar} ${area.progress}%`);
27150
- parts.push(`${area.doneCount}/${area.taskCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`);
27170
+ parts.push(
27171
+ `${area.doneCount}/${area.taskCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`
27172
+ );
27151
27173
  if (area.riskWarning) {
27152
27174
  parts.push(` \u26A0 ${area.riskWarning}`);
27153
27175
  }
@@ -27186,7 +27208,9 @@ function formatProgressReport(report) {
27186
27208
  if (report.proposedUpdates.length > 0) {
27187
27209
  parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
27188
27210
  for (const update of report.proposedUpdates) {
27189
- parts.push(` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
27211
+ parts.push(
27212
+ ` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
27213
+ );
27190
27214
  parts.push(` Reason: ${update.reason}`);
27191
27215
  }
27192
27216
  parts.push("");
@@ -27196,7 +27220,9 @@ function formatProgressReport(report) {
27196
27220
  if (report.appliedUpdates.length > 0) {
27197
27221
  parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
27198
27222
  for (const update of report.appliedUpdates) {
27199
- parts.push(` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
27223
+ parts.push(
27224
+ ` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
27225
+ );
27200
27226
  }
27201
27227
  parts.push("");
27202
27228
  }
@@ -27217,7 +27243,9 @@ function formatItemLine(parts, item, depth) {
27217
27243
  const progressLabel = ` ${item.progress}%`;
27218
27244
  const weightLabel = `w${item.weight}`;
27219
27245
  const sourceLabel = item.progressSource === "explicit" ? "" : item.progressSource === "comment-analysis" ? " (llm)" : " (est)";
27220
- parts.push(`${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${sourceLabel} (${weightLabel})${jiraLabel}${driftFlag}`);
27246
+ parts.push(
27247
+ `${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${sourceLabel} (${weightLabel})${jiraLabel}${driftFlag}`
27248
+ );
27221
27249
  if (item.commentSummary) {
27222
27250
  parts.push(`${indent} \u{1F4AC} ${item.commentSummary}`);
27223
27251
  }
@@ -27227,7 +27255,9 @@ function formatItemLine(parts, item, depth) {
27227
27255
  const doneMarker = link.isDone ? " \u2713" : "";
27228
27256
  const blockerResolved = link.isDone && BLOCKER_LINK_PATTERNS.some((p) => link.relationship.toLowerCase().includes(p.split(" ")[0])) ? " unblock signal" : "";
27229
27257
  const wontDo = WONT_DO_STATUSES.has(link.status.toLowerCase()) ? " \u26A0 needs review" : "";
27230
- parts.push(`${indent} ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}${blockerResolved}${wontDo}`);
27258
+ parts.push(
27259
+ `${indent} ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}${blockerResolved}${wontDo}`
27260
+ );
27231
27261
  const signal = item.linkedIssueSignals.find((s) => s.sourceKey === link.key);
27232
27262
  if (signal?.commentSummary) {
27233
27263
  parts.push(`${indent} \u{1F4AC} ${signal.commentSummary}`);
@@ -27246,6 +27276,7 @@ function progressBar6(pct) {
27246
27276
  var MAX_ARTIFACT_NODES = 50;
27247
27277
  var MAX_LLM_DEPTH = 3;
27248
27278
  var MAX_LLM_COMMENT_CHARS = 8e3;
27279
+ var DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD = 15;
27249
27280
  async function assessArtifact(store, client, host, options) {
27250
27281
  const visited = /* @__PURE__ */ new Set();
27251
27282
  return _assessArtifactRecursive(store, client, host, options, visited, 0);
@@ -27253,10 +27284,14 @@ async function assessArtifact(store, client, host, options) {
27253
27284
  async function _assessArtifactRecursive(store, client, host, options, visited, depth) {
27254
27285
  const errors = [];
27255
27286
  if (visited.has(options.artifactId)) {
27256
- return emptyArtifactReport(options.artifactId, [`Cycle detected: ${options.artifactId} already visited`]);
27287
+ return emptyArtifactReport(options.artifactId, [
27288
+ `Cycle detected: ${options.artifactId} already visited`
27289
+ ]);
27257
27290
  }
27258
27291
  if (visited.size >= MAX_ARTIFACT_NODES) {
27259
- return emptyArtifactReport(options.artifactId, [`Node cap reached (${MAX_ARTIFACT_NODES}), skipping ${options.artifactId}`]);
27292
+ return emptyArtifactReport(options.artifactId, [
27293
+ `Node cap reached (${MAX_ARTIFACT_NODES}), skipping ${options.artifactId}`
27294
+ ]);
27260
27295
  }
27261
27296
  visited.add(options.artifactId);
27262
27297
  const doc = store.get(options.artifactId);
@@ -27326,27 +27361,29 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27326
27361
  if (result.status === "fulfilled") {
27327
27362
  const { key, issue: li, comments: lc } = result.value;
27328
27363
  linkedJiraIssues.set(key, { issue: li, comments: lc });
27329
- const newLinks = collectLinkedIssues(li).filter((l) => l.relationship !== "subtask" && !jiraVisited.has(l.key));
27364
+ const newLinks = collectLinkedIssues(li).filter(
27365
+ (l) => l.relationship !== "subtask" && !jiraVisited.has(l.key)
27366
+ );
27330
27367
  for (const nl of newLinks) {
27331
27368
  jiraVisited.add(nl.key);
27332
27369
  queue.push(nl.key);
27333
27370
  }
27334
27371
  } else {
27335
27372
  const batchKey = batch[results.indexOf(result)];
27336
- errors.push(`Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
27373
+ errors.push(
27374
+ `Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
27375
+ );
27337
27376
  }
27338
27377
  }
27339
27378
  }
27340
- const { allLinks, allSignals } = collectTransitiveLinks(
27341
- issue2,
27342
- jiraIssues,
27343
- linkedJiraIssues
27344
- );
27379
+ const { allLinks, allSignals } = collectTransitiveLinks(issue2, jiraIssues, linkedJiraIssues);
27345
27380
  linkedIssues = allLinks;
27346
27381
  linkedIssueSignals = allSignals;
27347
27382
  analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
27348
27383
  } catch (err) {
27349
- errors.push(`Failed to fetch ${jiraKey}: ${err instanceof Error ? err.message : String(err)}`);
27384
+ errors.push(
27385
+ `Failed to fetch ${jiraKey}: ${err instanceof Error ? err.message : String(err)}`
27386
+ );
27350
27387
  }
27351
27388
  }
27352
27389
  const currentProgress = getEffectiveProgress(fm);
@@ -27387,7 +27424,11 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27387
27424
  const primaryHasComments = jiraKey ? (jiraIssues.get(jiraKey)?.comments.length ?? 0) > 0 : false;
27388
27425
  let commentAnalysisProgress = null;
27389
27426
  if (depth < MAX_LLM_DEPTH && jiraKey && primaryHasComments) {
27390
- const estimatedChars = estimateCommentTextSize(jiraIssues, linkedJiraIssues, linkedIssueSignals);
27427
+ const estimatedChars = estimateCommentTextSize(
27428
+ jiraIssues,
27429
+ linkedJiraIssues,
27430
+ linkedIssueSignals
27431
+ );
27391
27432
  if (estimatedChars <= MAX_LLM_COMMENT_CHARS) {
27392
27433
  try {
27393
27434
  const analysis = await analyzeSingleArtifactComments(
@@ -27402,14 +27443,16 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27402
27443
  commentSummary = analysis.summary;
27403
27444
  commentAnalysisProgress = analysis.progressEstimate;
27404
27445
  if (commentAnalysisProgress !== null) {
27405
- const hasExplicitProgress = "progress" in fm && typeof fm.progress === "number";
27406
- if (!hasExplicitProgress && !fm.progressOverride && commentAnalysisProgress !== currentProgress) {
27446
+ const divergence = Math.abs(commentAnalysisProgress - currentProgress);
27447
+ const threshold = options.progressDivergenceThreshold ?? DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD;
27448
+ if (divergence >= threshold && commentAnalysisProgress !== currentProgress) {
27449
+ const overrideWarning = fm.progressOverride ? " \u26A0 progressOverride is set \u2014 review before applying" : "";
27407
27450
  proposedUpdates.push({
27408
27451
  artifactId: fm.id,
27409
27452
  field: "progress",
27410
27453
  currentValue: currentProgress,
27411
27454
  proposedValue: commentAnalysisProgress,
27412
- reason: `Comment analysis estimates ${commentAnalysisProgress}% progress`
27455
+ reason: `Comment-derived estimate (${commentAnalysisProgress}%) diverges from current (${currentProgress}%) by ${divergence}pp${overrideWarning}`
27413
27456
  });
27414
27457
  }
27415
27458
  }
@@ -27422,7 +27465,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27422
27465
  const children = [];
27423
27466
  for (const childId of childIds) {
27424
27467
  if (visited.size >= MAX_ARTIFACT_NODES) {
27425
- errors.push(`Node cap reached (${MAX_ARTIFACT_NODES}), ${childIds.length - children.length} children skipped`);
27468
+ errors.push(
27469
+ `Node cap reached (${MAX_ARTIFACT_NODES}), ${childIds.length - children.length} children skipped`
27470
+ );
27426
27471
  break;
27427
27472
  }
27428
27473
  const childReport = await _assessArtifactRecursive(
@@ -27466,7 +27511,10 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27466
27511
  blockerProgressValue = blockerResult.blockerProgress;
27467
27512
  totalBlockersCount = blockerResult.totalBlockers;
27468
27513
  resolvedBlockersCount = blockerResult.resolvedBlockers;
27469
- const lastProgressUpdate = findLast(proposedUpdates, (u) => u.artifactId === fm.id && u.field === "progress");
27514
+ const lastProgressUpdate = findLast(
27515
+ proposedUpdates,
27516
+ (u) => u.artifactId === fm.id && u.field === "progress"
27517
+ );
27470
27518
  const implementationProgress = lastProgressUpdate ? lastProgressUpdate.proposedValue : currentProgress;
27471
27519
  const combinedProgress = Math.round(
27472
27520
  blockerResult.blockerProgress + implementationProgress * (1 - prerequisiteWeight)
@@ -27561,7 +27609,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27561
27609
  }
27562
27610
  store.update(fm.id, payload);
27563
27611
  } catch (err) {
27564
- errors.push(`Failed to persist assessment history: ${err instanceof Error ? err.message : String(err)}`);
27612
+ errors.push(
27613
+ `Failed to persist assessment history: ${err instanceof Error ? err.message : String(err)}`
27614
+ );
27565
27615
  }
27566
27616
  }
27567
27617
  return {
@@ -27621,9 +27671,7 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
27621
27671
  signals.push(`\u{1F6AB} Blocker \u2014 "${s.snippet}"`);
27622
27672
  }
27623
27673
  }
27624
- const blockingLinks = linkedIssues.filter(
27625
- (l) => l.relationship.toLowerCase().includes("block")
27626
- );
27674
+ const blockingLinks = linkedIssues.filter((l) => l.relationship.toLowerCase().includes("block"));
27627
27675
  const activeBlockers = blockingLinks.filter((l) => !l.isDone);
27628
27676
  const resolvedBlockers = blockingLinks.filter((l) => l.isDone);
27629
27677
  if (activeBlockers.length > 0) {
@@ -27632,7 +27680,9 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
27632
27680
  }
27633
27681
  }
27634
27682
  if (resolvedBlockers.length > 0 && activeBlockers.length === 0) {
27635
- signals.push(`\u2705 Unblocked \u2014 all blocking issues resolved: ${resolvedBlockers.map((l) => l.key).join(", ")}`);
27683
+ signals.push(
27684
+ `\u2705 Unblocked \u2014 all blocking issues resolved: ${resolvedBlockers.map((l) => l.key).join(", ")}`
27685
+ );
27636
27686
  }
27637
27687
  const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
27638
27688
  for (const l of wontDoLinks) {
@@ -27694,9 +27744,11 @@ async function analyzeSingleArtifactComments(artifactId, title, jiraKey, jiraSta
27694
27744
  const text = extractCommentText(c.body);
27695
27745
  return `[${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
27696
27746
  }).join("\n");
27697
- promptParts.push(`## ${artifactId} \u2014 ${title} (${jiraKey}, status: ${jiraStatus})
27747
+ promptParts.push(
27748
+ `## ${artifactId} \u2014 ${title} (${jiraKey}, status: ${jiraStatus})
27698
27749
  Comments:
27699
- ${commentTexts}`);
27750
+ ${commentTexts}`
27751
+ );
27700
27752
  }
27701
27753
  for (const signal of linkedIssueSignals) {
27702
27754
  const linkedData = linkedJiraIssues.get(signal.sourceKey);
@@ -27785,7 +27837,7 @@ function buildAssessmentSummary(commentSummary, commentAnalysisProgress, signals
27785
27837
  if (lastProgress) return lastProgress.proposedValue;
27786
27838
  return c.marvinProgress;
27787
27839
  });
27788
- const childDoneCount = children.filter((c, i) => {
27840
+ const childDoneCount = children.filter((c) => {
27789
27841
  const updates = c.appliedUpdates.length > 0 ? c.appliedUpdates : c.proposedUpdates;
27790
27842
  const lastStatus = findLast(updates, (u) => u.field === "status");
27791
27843
  const effectiveStatus = lastStatus ? String(lastStatus.proposedValue) : c.marvinStatus;
@@ -27821,7 +27873,8 @@ function formatArtifactReport(report) {
27821
27873
  parts.push(`## Jira State (${report.jiraKey})`);
27822
27874
  const jiraParts = [`Status: ${report.jiraStatus ?? "unknown"}`];
27823
27875
  if (report.jiraAssignee) jiraParts.push(`Assignee: ${report.jiraAssignee}`);
27824
- if (report.jiraSubtaskProgress !== null) jiraParts.push(`Subtask progress: ${report.jiraSubtaskProgress}%`);
27876
+ if (report.jiraSubtaskProgress !== null)
27877
+ jiraParts.push(`Subtask progress: ${report.jiraSubtaskProgress}%`);
27825
27878
  parts.push(jiraParts.join(" | "));
27826
27879
  if (report.statusDrift) {
27827
27880
  parts.push(`\u26A0 Drift: ${report.marvinStatus} \u2192 ${report.proposedMarvinStatus}`);
@@ -27842,7 +27895,9 @@ function formatArtifactReport(report) {
27842
27895
  if (report.totalBlockers > 0) {
27843
27896
  parts.push(`## Blocker Resolution`);
27844
27897
  const bpLabel = report.blockerProgress !== null ? `${report.blockerProgress}%` : "n/a (skipped)";
27845
- parts.push(` ${report.resolvedBlockers}/${report.totalBlockers} blockers resolved \u2192 ${bpLabel} prerequisite progress`);
27898
+ parts.push(
27899
+ ` ${report.resolvedBlockers}/${report.totalBlockers} blockers resolved \u2192 ${bpLabel} prerequisite progress`
27900
+ );
27846
27901
  parts.push("");
27847
27902
  }
27848
27903
  if (report.children.length > 0) {
@@ -27851,7 +27906,9 @@ function formatArtifactReport(report) {
27851
27906
  report.children.reduce((s, c) => s + c.marvinProgress, 0) / report.children.length
27852
27907
  );
27853
27908
  const bar = progressBar6(childProgress);
27854
- parts.push(`## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`);
27909
+ parts.push(
27910
+ `## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`
27911
+ );
27855
27912
  for (const child of report.children) {
27856
27913
  formatArtifactChild(parts, child, 1);
27857
27914
  }
@@ -27861,7 +27918,9 @@ function formatArtifactReport(report) {
27861
27918
  parts.push(`## Linked Issues (${report.linkedIssues.length})`);
27862
27919
  for (const link of report.linkedIssues) {
27863
27920
  const doneMarker = link.isDone ? " \u2713" : "";
27864
- parts.push(` ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}`);
27921
+ parts.push(
27922
+ ` ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}`
27923
+ );
27865
27924
  const signal = report.linkedIssueSignals.find((s) => s.sourceKey === link.key);
27866
27925
  if (signal?.commentSummary) {
27867
27926
  parts.push(` \u{1F4AC} ${signal.commentSummary}`);
@@ -27879,7 +27938,9 @@ function formatArtifactReport(report) {
27879
27938
  if (report.proposedUpdates.length > 0) {
27880
27939
  parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
27881
27940
  for (const update of report.proposedUpdates) {
27882
- parts.push(` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
27941
+ parts.push(
27942
+ ` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
27943
+ );
27883
27944
  parts.push(` Reason: ${update.reason}`);
27884
27945
  }
27885
27946
  parts.push("");
@@ -27889,7 +27950,9 @@ function formatArtifactReport(report) {
27889
27950
  if (report.appliedUpdates.length > 0) {
27890
27951
  parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
27891
27952
  for (const update of report.appliedUpdates) {
27892
- parts.push(` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
27953
+ parts.push(
27954
+ ` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
27955
+ );
27893
27956
  }
27894
27957
  parts.push("");
27895
27958
  }
@@ -27912,7 +27975,9 @@ function formatArtifactChild(parts, child, depth) {
27912
27975
  if (s.startsWith("\u2705 No active")) continue;
27913
27976
  signalHints.push(s);
27914
27977
  }
27915
- parts.push(`${indent}${icon} ${child.artifactId} \u2014 ${child.title} [${child.marvinStatus}] ${child.marvinProgress}%${jiraLabel}${driftLabel}`);
27978
+ parts.push(
27979
+ `${indent}${icon} ${child.artifactId} \u2014 ${child.title} [${child.marvinStatus}] ${child.marvinProgress}%${jiraLabel}${driftLabel}`
27980
+ );
27916
27981
  if (child.commentSummary) {
27917
27982
  parts.push(`${indent} \u{1F4AC} ${child.commentSummary}`);
27918
27983
  }
@@ -28747,7 +28812,8 @@ function createJiraTools(store, projectConfig) {
28747
28812
  {
28748
28813
  artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'T-063', 'A-151', 'E-003')"),
28749
28814
  applyUpdates: external_exports.boolean().optional().describe("Apply proposed status/progress updates to the artifact (default false)"),
28750
- 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.")
28815
+ 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."),
28816
+ 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).")
28751
28817
  },
28752
28818
  async (args) => {
28753
28819
  const jira = createJiraClient(jiraUserConfig);
@@ -28760,6 +28826,7 @@ function createJiraTools(store, projectConfig) {
28760
28826
  artifactId: args.artifactId,
28761
28827
  applyUpdates: args.applyUpdates ?? false,
28762
28828
  prerequisiteWeight: args.prerequisiteWeight,
28829
+ progressDivergenceThreshold: args.progressDivergenceThreshold,
28763
28830
  statusMap
28764
28831
  }
28765
28832
  );
@@ -34428,7 +34495,7 @@ function createProgram() {
34428
34495
  const program = new Command();
34429
34496
  program.name("marvin").description(
34430
34497
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
34431
- ).version("0.5.27");
34498
+ ).version("0.5.28");
34432
34499
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
34433
34500
  await initCommand();
34434
34501
  });