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.
@@ -20078,10 +20078,16 @@ function resolveWeight(complexity) {
20078
20078
  function resolveProgress(frontmatter, commentAnalysisProgress) {
20079
20079
  const hasExplicitProgress = "progress" in frontmatter && typeof frontmatter.progress === "number";
20080
20080
  if (hasExplicitProgress) {
20081
- return { progress: Math.max(0, Math.min(100, Math.round(frontmatter.progress))), progressSource: "explicit" };
20081
+ return {
20082
+ progress: Math.max(0, Math.min(100, Math.round(frontmatter.progress))),
20083
+ progressSource: "explicit"
20084
+ };
20082
20085
  }
20083
20086
  if (commentAnalysisProgress !== null) {
20084
- return { progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))), progressSource: "comment-analysis" };
20087
+ return {
20088
+ progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))),
20089
+ progressSource: "comment-analysis"
20090
+ };
20085
20091
  }
20086
20092
  const status = frontmatter.status;
20087
20093
  const defaultProgress = STATUS_PROGRESS_DEFAULTS[status] ?? 0;
@@ -20106,7 +20112,13 @@ async function assessSprintProgress(store, client, host, options = {}) {
20106
20112
  sprintId: options.sprintId ?? "unknown",
20107
20113
  sprintTitle: "Sprint not found",
20108
20114
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
20109
- timeline: { startDate: null, endDate: null, daysRemaining: 0, totalDays: 0, percentComplete: 0 },
20115
+ timeline: {
20116
+ startDate: null,
20117
+ endDate: null,
20118
+ daysRemaining: 0,
20119
+ totalDays: 0,
20120
+ percentComplete: 0
20121
+ },
20110
20122
  overallProgress: 0,
20111
20123
  itemReports: [],
20112
20124
  focusAreas: [],
@@ -20114,7 +20126,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
20114
20126
  blockers: [],
20115
20127
  proposedUpdates: [],
20116
20128
  appliedUpdates: [],
20117
- errors: [`Sprint ${options.sprintId ?? "(active)"} not found. Create a sprint artifact first.`]
20129
+ errors: [
20130
+ `Sprint ${options.sprintId ?? "(active)"} not found. Create a sprint artifact first.`
20131
+ ]
20118
20132
  };
20119
20133
  }
20120
20134
  const sprintTag = `sprint:${sprintData.sprint.id}`;
@@ -20158,7 +20172,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
20158
20172
  });
20159
20173
  } else {
20160
20174
  const batchKey = batch[results.indexOf(result)];
20161
- errors.push(`Failed to fetch ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
20175
+ errors.push(
20176
+ `Failed to fetch ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
20177
+ );
20162
20178
  }
20163
20179
  }
20164
20180
  }
@@ -20200,12 +20216,16 @@ async function assessSprintProgress(store, client, host, options = {}) {
20200
20216
  }
20201
20217
  } else {
20202
20218
  const batchKey = batch[results.indexOf(result)];
20203
- errors.push(`Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
20219
+ errors.push(
20220
+ `Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
20221
+ );
20204
20222
  }
20205
20223
  }
20206
20224
  }
20207
20225
  if (queue.length > 0) {
20208
- errors.push(`Link traversal capped at ${MAX_LINKED_ISSUES} linked issues (${queue.length} remaining undiscovered)`);
20226
+ errors.push(
20227
+ `Link traversal capped at ${MAX_LINKED_ISSUES} linked issues (${queue.length} remaining undiscovered)`
20228
+ );
20209
20229
  }
20210
20230
  }
20211
20231
  const proposedUpdates = [];
@@ -20283,12 +20303,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
20283
20303
  );
20284
20304
  itemLinkedIssues = allLinks;
20285
20305
  itemLinkedIssueSignals.push(...allSignals);
20286
- analyzeLinkedIssueSignals(
20287
- allLinks,
20288
- fm,
20289
- jiraKey,
20290
- proposedUpdates
20291
- );
20306
+ analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
20292
20307
  }
20293
20308
  const report = {
20294
20309
  id: fm.id,
@@ -20407,10 +20422,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
20407
20422
  }
20408
20423
  if (options.traverseLinks) {
20409
20424
  try {
20410
- const linkedSummaries = await analyzeLinkedIssueComments(
20411
- itemReports,
20412
- linkedJiraIssues
20413
- );
20425
+ const linkedSummaries = await analyzeLinkedIssueComments(itemReports, linkedJiraIssues);
20414
20426
  for (const [artifactId, signalSummaries] of linkedSummaries) {
20415
20427
  const report = itemReports.find((r) => r.id === artifactId);
20416
20428
  if (!report) continue;
@@ -20422,7 +20434,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
20422
20434
  }
20423
20435
  }
20424
20436
  } catch (err) {
20425
- errors.push(`Linked issue comment analysis failed: ${err instanceof Error ? err.message : String(err)}`);
20437
+ errors.push(
20438
+ `Linked issue comment analysis failed: ${err instanceof Error ? err.message : String(err)}`
20439
+ );
20426
20440
  }
20427
20441
  }
20428
20442
  }
@@ -20494,9 +20508,11 @@ async function analyzeCommentsForProgress(items, jiraIssues, itemJiraKeys) {
20494
20508
  const text = extractCommentText(c.body);
20495
20509
  return ` [${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
20496
20510
  }).join("\n");
20497
- promptParts.push(`## ${item.id} \u2014 ${item.title} (${jiraKey}, Jira status: ${item.jiraStatus})
20511
+ promptParts.push(
20512
+ `## ${item.id} \u2014 ${item.title} (${jiraKey}, Jira status: ${item.jiraStatus})
20498
20513
  Comments:
20499
- ${commentTexts}`);
20514
+ ${commentTexts}`
20515
+ );
20500
20516
  }
20501
20517
  if (promptParts.length === 0) return summaries;
20502
20518
  const prompt = promptParts.join("\n\n");
@@ -20569,7 +20585,9 @@ function collectTransitiveLinks(primaryIssue, primaryIssues, linkedJiraIssues) {
20569
20585
  commentSummary: null
20570
20586
  });
20571
20587
  }
20572
- const nextLinks = collectLinkedIssues(linkedData.issue).filter((l) => l.relationship !== "subtask" && !visited.has(l.key));
20588
+ const nextLinks = collectLinkedIssues(linkedData.issue).filter(
20589
+ (l) => l.relationship !== "subtask" && !visited.has(l.key)
20590
+ );
20573
20591
  for (const next of nextLinks) {
20574
20592
  visited.add(next.key);
20575
20593
  queue.push(next);
@@ -20593,9 +20611,7 @@ function analyzeLinkedIssueSignals(linkedIssues, frontmatter, jiraKey, proposedU
20593
20611
  reason: `All blocking issues resolved: ${blockerLinks.map((l) => l.key).join(", ")}`
20594
20612
  });
20595
20613
  }
20596
- const wontDoLinks = linkedIssues.filter(
20597
- (l) => WONT_DO_STATUSES.has(l.status.toLowerCase())
20598
- );
20614
+ const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
20599
20615
  if (wontDoLinks.length > 0) {
20600
20616
  proposedUpdates.push({
20601
20617
  artifactId: frontmatter.id,
@@ -20669,7 +20685,9 @@ ${linkedParts.join("\n")}`);
20669
20685
  for (const [artifactId, linkedSummaries] of Object.entries(parsed)) {
20670
20686
  if (typeof linkedSummaries === "object" && linkedSummaries !== null) {
20671
20687
  const signalMap = /* @__PURE__ */ new Map();
20672
- for (const [key, summary] of Object.entries(linkedSummaries)) {
20688
+ for (const [key, summary] of Object.entries(
20689
+ linkedSummaries
20690
+ )) {
20673
20691
  if (typeof summary === "string") {
20674
20692
  signalMap.set(key, summary);
20675
20693
  }
@@ -20708,7 +20726,9 @@ function formatProgressReport(report) {
20708
20726
  if (report.timeline.startDate && report.timeline.endDate) {
20709
20727
  parts.push(`## Timeline`);
20710
20728
  parts.push(`${report.timeline.startDate} \u2192 ${report.timeline.endDate}`);
20711
- parts.push(`Days remaining: ${report.timeline.daysRemaining} / ${report.timeline.totalDays} (${report.timeline.percentComplete}% elapsed)`);
20729
+ parts.push(
20730
+ `Days remaining: ${report.timeline.daysRemaining} / ${report.timeline.totalDays} (${report.timeline.percentComplete}% elapsed)`
20731
+ );
20712
20732
  parts.push(`Overall progress: ${report.overallProgress}%`);
20713
20733
  parts.push("");
20714
20734
  }
@@ -20718,7 +20738,9 @@ function formatProgressReport(report) {
20718
20738
  for (const area of report.focusAreas) {
20719
20739
  const bar = progressBar(area.progress);
20720
20740
  parts.push(`### ${area.name} ${bar} ${area.progress}%`);
20721
- parts.push(`${area.doneCount}/${area.taskCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`);
20741
+ parts.push(
20742
+ `${area.doneCount}/${area.taskCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`
20743
+ );
20722
20744
  if (area.riskWarning) {
20723
20745
  parts.push(` \u26A0 ${area.riskWarning}`);
20724
20746
  }
@@ -20757,7 +20779,9 @@ function formatProgressReport(report) {
20757
20779
  if (report.proposedUpdates.length > 0) {
20758
20780
  parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
20759
20781
  for (const update of report.proposedUpdates) {
20760
- parts.push(` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
20782
+ parts.push(
20783
+ ` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
20784
+ );
20761
20785
  parts.push(` Reason: ${update.reason}`);
20762
20786
  }
20763
20787
  parts.push("");
@@ -20767,7 +20791,9 @@ function formatProgressReport(report) {
20767
20791
  if (report.appliedUpdates.length > 0) {
20768
20792
  parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
20769
20793
  for (const update of report.appliedUpdates) {
20770
- parts.push(` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
20794
+ parts.push(
20795
+ ` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
20796
+ );
20771
20797
  }
20772
20798
  parts.push("");
20773
20799
  }
@@ -20788,7 +20814,9 @@ function formatItemLine(parts, item, depth) {
20788
20814
  const progressLabel = ` ${item.progress}%`;
20789
20815
  const weightLabel = `w${item.weight}`;
20790
20816
  const sourceLabel = item.progressSource === "explicit" ? "" : item.progressSource === "comment-analysis" ? " (llm)" : " (est)";
20791
- parts.push(`${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${sourceLabel} (${weightLabel})${jiraLabel}${driftFlag}`);
20817
+ parts.push(
20818
+ `${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${sourceLabel} (${weightLabel})${jiraLabel}${driftFlag}`
20819
+ );
20792
20820
  if (item.commentSummary) {
20793
20821
  parts.push(`${indent} \u{1F4AC} ${item.commentSummary}`);
20794
20822
  }
@@ -20798,7 +20826,9 @@ function formatItemLine(parts, item, depth) {
20798
20826
  const doneMarker = link.isDone ? " \u2713" : "";
20799
20827
  const blockerResolved = link.isDone && BLOCKER_LINK_PATTERNS.some((p) => link.relationship.toLowerCase().includes(p.split(" ")[0])) ? " unblock signal" : "";
20800
20828
  const wontDo = WONT_DO_STATUSES.has(link.status.toLowerCase()) ? " \u26A0 needs review" : "";
20801
- parts.push(`${indent} ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}${blockerResolved}${wontDo}`);
20829
+ parts.push(
20830
+ `${indent} ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}${blockerResolved}${wontDo}`
20831
+ );
20802
20832
  const signal = item.linkedIssueSignals.find((s) => s.sourceKey === link.key);
20803
20833
  if (signal?.commentSummary) {
20804
20834
  parts.push(`${indent} \u{1F4AC} ${signal.commentSummary}`);
@@ -20817,6 +20847,7 @@ function progressBar(pct) {
20817
20847
  var MAX_ARTIFACT_NODES = 50;
20818
20848
  var MAX_LLM_DEPTH = 3;
20819
20849
  var MAX_LLM_COMMENT_CHARS = 8e3;
20850
+ var DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD = 15;
20820
20851
  async function assessArtifact(store, client, host, options) {
20821
20852
  const visited = /* @__PURE__ */ new Set();
20822
20853
  return _assessArtifactRecursive(store, client, host, options, visited, 0);
@@ -20824,10 +20855,14 @@ async function assessArtifact(store, client, host, options) {
20824
20855
  async function _assessArtifactRecursive(store, client, host, options, visited, depth) {
20825
20856
  const errors = [];
20826
20857
  if (visited.has(options.artifactId)) {
20827
- return emptyArtifactReport(options.artifactId, [`Cycle detected: ${options.artifactId} already visited`]);
20858
+ return emptyArtifactReport(options.artifactId, [
20859
+ `Cycle detected: ${options.artifactId} already visited`
20860
+ ]);
20828
20861
  }
20829
20862
  if (visited.size >= MAX_ARTIFACT_NODES) {
20830
- return emptyArtifactReport(options.artifactId, [`Node cap reached (${MAX_ARTIFACT_NODES}), skipping ${options.artifactId}`]);
20863
+ return emptyArtifactReport(options.artifactId, [
20864
+ `Node cap reached (${MAX_ARTIFACT_NODES}), skipping ${options.artifactId}`
20865
+ ]);
20831
20866
  }
20832
20867
  visited.add(options.artifactId);
20833
20868
  const doc = store.get(options.artifactId);
@@ -20897,27 +20932,29 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
20897
20932
  if (result.status === "fulfilled") {
20898
20933
  const { key, issue: li, comments: lc } = result.value;
20899
20934
  linkedJiraIssues.set(key, { issue: li, comments: lc });
20900
- const newLinks = collectLinkedIssues(li).filter((l) => l.relationship !== "subtask" && !jiraVisited.has(l.key));
20935
+ const newLinks = collectLinkedIssues(li).filter(
20936
+ (l) => l.relationship !== "subtask" && !jiraVisited.has(l.key)
20937
+ );
20901
20938
  for (const nl of newLinks) {
20902
20939
  jiraVisited.add(nl.key);
20903
20940
  queue.push(nl.key);
20904
20941
  }
20905
20942
  } else {
20906
20943
  const batchKey = batch[results.indexOf(result)];
20907
- errors.push(`Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
20944
+ errors.push(
20945
+ `Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
20946
+ );
20908
20947
  }
20909
20948
  }
20910
20949
  }
20911
- const { allLinks, allSignals } = collectTransitiveLinks(
20912
- issue2,
20913
- jiraIssues,
20914
- linkedJiraIssues
20915
- );
20950
+ const { allLinks, allSignals } = collectTransitiveLinks(issue2, jiraIssues, linkedJiraIssues);
20916
20951
  linkedIssues = allLinks;
20917
20952
  linkedIssueSignals = allSignals;
20918
20953
  analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
20919
20954
  } catch (err) {
20920
- errors.push(`Failed to fetch ${jiraKey}: ${err instanceof Error ? err.message : String(err)}`);
20955
+ errors.push(
20956
+ `Failed to fetch ${jiraKey}: ${err instanceof Error ? err.message : String(err)}`
20957
+ );
20921
20958
  }
20922
20959
  }
20923
20960
  const currentProgress = getEffectiveProgress(fm);
@@ -20958,7 +20995,11 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
20958
20995
  const primaryHasComments = jiraKey ? (jiraIssues.get(jiraKey)?.comments.length ?? 0) > 0 : false;
20959
20996
  let commentAnalysisProgress = null;
20960
20997
  if (depth < MAX_LLM_DEPTH && jiraKey && primaryHasComments) {
20961
- const estimatedChars = estimateCommentTextSize(jiraIssues, linkedJiraIssues, linkedIssueSignals);
20998
+ const estimatedChars = estimateCommentTextSize(
20999
+ jiraIssues,
21000
+ linkedJiraIssues,
21001
+ linkedIssueSignals
21002
+ );
20962
21003
  if (estimatedChars <= MAX_LLM_COMMENT_CHARS) {
20963
21004
  try {
20964
21005
  const analysis = await analyzeSingleArtifactComments(
@@ -20973,14 +21014,16 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
20973
21014
  commentSummary = analysis.summary;
20974
21015
  commentAnalysisProgress = analysis.progressEstimate;
20975
21016
  if (commentAnalysisProgress !== null) {
20976
- const hasExplicitProgress = "progress" in fm && typeof fm.progress === "number";
20977
- if (!hasExplicitProgress && !fm.progressOverride && commentAnalysisProgress !== currentProgress) {
21017
+ const divergence = Math.abs(commentAnalysisProgress - currentProgress);
21018
+ const threshold = options.progressDivergenceThreshold ?? DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD;
21019
+ if (divergence >= threshold && commentAnalysisProgress !== currentProgress) {
21020
+ const overrideWarning = fm.progressOverride ? " \u26A0 progressOverride is set \u2014 review before applying" : "";
20978
21021
  proposedUpdates.push({
20979
21022
  artifactId: fm.id,
20980
21023
  field: "progress",
20981
21024
  currentValue: currentProgress,
20982
21025
  proposedValue: commentAnalysisProgress,
20983
- reason: `Comment analysis estimates ${commentAnalysisProgress}% progress`
21026
+ reason: `Comment-derived estimate (${commentAnalysisProgress}%) diverges from current (${currentProgress}%) by ${divergence}pp${overrideWarning}`
20984
21027
  });
20985
21028
  }
20986
21029
  }
@@ -20993,7 +21036,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
20993
21036
  const children = [];
20994
21037
  for (const childId of childIds) {
20995
21038
  if (visited.size >= MAX_ARTIFACT_NODES) {
20996
- errors.push(`Node cap reached (${MAX_ARTIFACT_NODES}), ${childIds.length - children.length} children skipped`);
21039
+ errors.push(
21040
+ `Node cap reached (${MAX_ARTIFACT_NODES}), ${childIds.length - children.length} children skipped`
21041
+ );
20997
21042
  break;
20998
21043
  }
20999
21044
  const childReport = await _assessArtifactRecursive(
@@ -21037,7 +21082,10 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
21037
21082
  blockerProgressValue = blockerResult.blockerProgress;
21038
21083
  totalBlockersCount = blockerResult.totalBlockers;
21039
21084
  resolvedBlockersCount = blockerResult.resolvedBlockers;
21040
- const lastProgressUpdate = findLast(proposedUpdates, (u) => u.artifactId === fm.id && u.field === "progress");
21085
+ const lastProgressUpdate = findLast(
21086
+ proposedUpdates,
21087
+ (u) => u.artifactId === fm.id && u.field === "progress"
21088
+ );
21041
21089
  const implementationProgress = lastProgressUpdate ? lastProgressUpdate.proposedValue : currentProgress;
21042
21090
  const combinedProgress = Math.round(
21043
21091
  blockerResult.blockerProgress + implementationProgress * (1 - prerequisiteWeight)
@@ -21132,7 +21180,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
21132
21180
  }
21133
21181
  store.update(fm.id, payload);
21134
21182
  } catch (err) {
21135
- errors.push(`Failed to persist assessment history: ${err instanceof Error ? err.message : String(err)}`);
21183
+ errors.push(
21184
+ `Failed to persist assessment history: ${err instanceof Error ? err.message : String(err)}`
21185
+ );
21136
21186
  }
21137
21187
  }
21138
21188
  return {
@@ -21192,9 +21242,7 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
21192
21242
  signals.push(`\u{1F6AB} Blocker \u2014 "${s.snippet}"`);
21193
21243
  }
21194
21244
  }
21195
- const blockingLinks = linkedIssues.filter(
21196
- (l) => l.relationship.toLowerCase().includes("block")
21197
- );
21245
+ const blockingLinks = linkedIssues.filter((l) => l.relationship.toLowerCase().includes("block"));
21198
21246
  const activeBlockers = blockingLinks.filter((l) => !l.isDone);
21199
21247
  const resolvedBlockers = blockingLinks.filter((l) => l.isDone);
21200
21248
  if (activeBlockers.length > 0) {
@@ -21203,7 +21251,9 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
21203
21251
  }
21204
21252
  }
21205
21253
  if (resolvedBlockers.length > 0 && activeBlockers.length === 0) {
21206
- signals.push(`\u2705 Unblocked \u2014 all blocking issues resolved: ${resolvedBlockers.map((l) => l.key).join(", ")}`);
21254
+ signals.push(
21255
+ `\u2705 Unblocked \u2014 all blocking issues resolved: ${resolvedBlockers.map((l) => l.key).join(", ")}`
21256
+ );
21207
21257
  }
21208
21258
  const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
21209
21259
  for (const l of wontDoLinks) {
@@ -21265,9 +21315,11 @@ async function analyzeSingleArtifactComments(artifactId, title, jiraKey, jiraSta
21265
21315
  const text = extractCommentText(c.body);
21266
21316
  return `[${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
21267
21317
  }).join("\n");
21268
- promptParts.push(`## ${artifactId} \u2014 ${title} (${jiraKey}, status: ${jiraStatus})
21318
+ promptParts.push(
21319
+ `## ${artifactId} \u2014 ${title} (${jiraKey}, status: ${jiraStatus})
21269
21320
  Comments:
21270
- ${commentTexts}`);
21321
+ ${commentTexts}`
21322
+ );
21271
21323
  }
21272
21324
  for (const signal of linkedIssueSignals) {
21273
21325
  const linkedData = linkedJiraIssues.get(signal.sourceKey);
@@ -21356,7 +21408,7 @@ function buildAssessmentSummary(commentSummary, commentAnalysisProgress, signals
21356
21408
  if (lastProgress) return lastProgress.proposedValue;
21357
21409
  return c.marvinProgress;
21358
21410
  });
21359
- const childDoneCount = children.filter((c, i) => {
21411
+ const childDoneCount = children.filter((c) => {
21360
21412
  const updates = c.appliedUpdates.length > 0 ? c.appliedUpdates : c.proposedUpdates;
21361
21413
  const lastStatus = findLast(updates, (u) => u.field === "status");
21362
21414
  const effectiveStatus = lastStatus ? String(lastStatus.proposedValue) : c.marvinStatus;
@@ -21392,7 +21444,8 @@ function formatArtifactReport(report) {
21392
21444
  parts.push(`## Jira State (${report.jiraKey})`);
21393
21445
  const jiraParts = [`Status: ${report.jiraStatus ?? "unknown"}`];
21394
21446
  if (report.jiraAssignee) jiraParts.push(`Assignee: ${report.jiraAssignee}`);
21395
- if (report.jiraSubtaskProgress !== null) jiraParts.push(`Subtask progress: ${report.jiraSubtaskProgress}%`);
21447
+ if (report.jiraSubtaskProgress !== null)
21448
+ jiraParts.push(`Subtask progress: ${report.jiraSubtaskProgress}%`);
21396
21449
  parts.push(jiraParts.join(" | "));
21397
21450
  if (report.statusDrift) {
21398
21451
  parts.push(`\u26A0 Drift: ${report.marvinStatus} \u2192 ${report.proposedMarvinStatus}`);
@@ -21413,7 +21466,9 @@ function formatArtifactReport(report) {
21413
21466
  if (report.totalBlockers > 0) {
21414
21467
  parts.push(`## Blocker Resolution`);
21415
21468
  const bpLabel = report.blockerProgress !== null ? `${report.blockerProgress}%` : "n/a (skipped)";
21416
- parts.push(` ${report.resolvedBlockers}/${report.totalBlockers} blockers resolved \u2192 ${bpLabel} prerequisite progress`);
21469
+ parts.push(
21470
+ ` ${report.resolvedBlockers}/${report.totalBlockers} blockers resolved \u2192 ${bpLabel} prerequisite progress`
21471
+ );
21417
21472
  parts.push("");
21418
21473
  }
21419
21474
  if (report.children.length > 0) {
@@ -21422,7 +21477,9 @@ function formatArtifactReport(report) {
21422
21477
  report.children.reduce((s, c) => s + c.marvinProgress, 0) / report.children.length
21423
21478
  );
21424
21479
  const bar = progressBar(childProgress);
21425
- parts.push(`## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`);
21480
+ parts.push(
21481
+ `## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`
21482
+ );
21426
21483
  for (const child of report.children) {
21427
21484
  formatArtifactChild(parts, child, 1);
21428
21485
  }
@@ -21432,7 +21489,9 @@ function formatArtifactReport(report) {
21432
21489
  parts.push(`## Linked Issues (${report.linkedIssues.length})`);
21433
21490
  for (const link of report.linkedIssues) {
21434
21491
  const doneMarker = link.isDone ? " \u2713" : "";
21435
- parts.push(` ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}`);
21492
+ parts.push(
21493
+ ` ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}`
21494
+ );
21436
21495
  const signal = report.linkedIssueSignals.find((s) => s.sourceKey === link.key);
21437
21496
  if (signal?.commentSummary) {
21438
21497
  parts.push(` \u{1F4AC} ${signal.commentSummary}`);
@@ -21450,7 +21509,9 @@ function formatArtifactReport(report) {
21450
21509
  if (report.proposedUpdates.length > 0) {
21451
21510
  parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
21452
21511
  for (const update of report.proposedUpdates) {
21453
- parts.push(` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
21512
+ parts.push(
21513
+ ` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
21514
+ );
21454
21515
  parts.push(` Reason: ${update.reason}`);
21455
21516
  }
21456
21517
  parts.push("");
@@ -21460,7 +21521,9 @@ function formatArtifactReport(report) {
21460
21521
  if (report.appliedUpdates.length > 0) {
21461
21522
  parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
21462
21523
  for (const update of report.appliedUpdates) {
21463
- parts.push(` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`);
21524
+ parts.push(
21525
+ ` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
21526
+ );
21464
21527
  }
21465
21528
  parts.push("");
21466
21529
  }
@@ -21483,7 +21546,9 @@ function formatArtifactChild(parts, child, depth) {
21483
21546
  if (s.startsWith("\u2705 No active")) continue;
21484
21547
  signalHints.push(s);
21485
21548
  }
21486
- parts.push(`${indent}${icon} ${child.artifactId} \u2014 ${child.title} [${child.marvinStatus}] ${child.marvinProgress}%${jiraLabel}${driftLabel}`);
21549
+ parts.push(
21550
+ `${indent}${icon} ${child.artifactId} \u2014 ${child.title} [${child.marvinStatus}] ${child.marvinProgress}%${jiraLabel}${driftLabel}`
21551
+ );
21487
21552
  if (child.commentSummary) {
21488
21553
  parts.push(`${indent} \u{1F4AC} ${child.commentSummary}`);
21489
21554
  }
@@ -22318,7 +22383,8 @@ function createJiraTools(store, projectConfig) {
22318
22383
  {
22319
22384
  artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'T-063', 'A-151', 'E-003')"),
22320
22385
  applyUpdates: external_exports.boolean().optional().describe("Apply proposed status/progress updates to the artifact (default false)"),
22321
- 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.")
22386
+ 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."),
22387
+ 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).")
22322
22388
  },
22323
22389
  async (args) => {
22324
22390
  const jira = createJiraClient(jiraUserConfig);
@@ -22331,6 +22397,7 @@ function createJiraTools(store, projectConfig) {
22331
22397
  artifactId: args.artifactId,
22332
22398
  applyUpdates: args.applyUpdates ?? false,
22333
22399
  prerequisiteWeight: args.prerequisiteWeight,
22400
+ progressDivergenceThreshold: args.progressDivergenceThreshold,
22334
22401
  statusMap
22335
22402
  }
22336
22403
  );