mrvn-cli 0.5.26 → 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,
@@ -20606,6 +20622,15 @@ function analyzeLinkedIssueSignals(linkedIssues, frontmatter, jiraKey, proposedU
20606
20622
  });
20607
20623
  }
20608
20624
  }
20625
+ function computeBlockerProgress(linkedIssues, prerequisiteWeight) {
20626
+ const blockerLinks = linkedIssues.filter(
20627
+ (l) => BLOCKER_LINK_PATTERNS.some((p) => l.relationship.toLowerCase().includes(p.split(" ")[0]))
20628
+ );
20629
+ if (blockerLinks.length === 0) return null;
20630
+ const resolved = blockerLinks.filter((l) => l.isDone).length;
20631
+ const blockerProgress = Math.round(resolved / blockerLinks.length * prerequisiteWeight * 100);
20632
+ return { blockerProgress, totalBlockers: blockerLinks.length, resolvedBlockers: resolved };
20633
+ }
20609
20634
  var LINKED_COMMENT_ANALYSIS_PROMPT = `You are a delivery management assistant analyzing Jira comments from linked issues for progress signals.
20610
20635
 
20611
20636
  For each linked issue below, read the comments and produce a 1-sentence summary focused on: impact on the parent issue, blockers, or decisions.
@@ -20660,7 +20685,9 @@ ${linkedParts.join("\n")}`);
20660
20685
  for (const [artifactId, linkedSummaries] of Object.entries(parsed)) {
20661
20686
  if (typeof linkedSummaries === "object" && linkedSummaries !== null) {
20662
20687
  const signalMap = /* @__PURE__ */ new Map();
20663
- for (const [key, summary] of Object.entries(linkedSummaries)) {
20688
+ for (const [key, summary] of Object.entries(
20689
+ linkedSummaries
20690
+ )) {
20664
20691
  if (typeof summary === "string") {
20665
20692
  signalMap.set(key, summary);
20666
20693
  }
@@ -20699,7 +20726,9 @@ function formatProgressReport(report) {
20699
20726
  if (report.timeline.startDate && report.timeline.endDate) {
20700
20727
  parts.push(`## Timeline`);
20701
20728
  parts.push(`${report.timeline.startDate} \u2192 ${report.timeline.endDate}`);
20702
- 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
+ );
20703
20732
  parts.push(`Overall progress: ${report.overallProgress}%`);
20704
20733
  parts.push("");
20705
20734
  }
@@ -20709,7 +20738,9 @@ function formatProgressReport(report) {
20709
20738
  for (const area of report.focusAreas) {
20710
20739
  const bar = progressBar(area.progress);
20711
20740
  parts.push(`### ${area.name} ${bar} ${area.progress}%`);
20712
- 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
+ );
20713
20744
  if (area.riskWarning) {
20714
20745
  parts.push(` \u26A0 ${area.riskWarning}`);
20715
20746
  }
@@ -20748,7 +20779,9 @@ function formatProgressReport(report) {
20748
20779
  if (report.proposedUpdates.length > 0) {
20749
20780
  parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
20750
20781
  for (const update of report.proposedUpdates) {
20751
- 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
+ );
20752
20785
  parts.push(` Reason: ${update.reason}`);
20753
20786
  }
20754
20787
  parts.push("");
@@ -20758,7 +20791,9 @@ function formatProgressReport(report) {
20758
20791
  if (report.appliedUpdates.length > 0) {
20759
20792
  parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
20760
20793
  for (const update of report.appliedUpdates) {
20761
- 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
+ );
20762
20797
  }
20763
20798
  parts.push("");
20764
20799
  }
@@ -20779,7 +20814,9 @@ function formatItemLine(parts, item, depth) {
20779
20814
  const progressLabel = ` ${item.progress}%`;
20780
20815
  const weightLabel = `w${item.weight}`;
20781
20816
  const sourceLabel = item.progressSource === "explicit" ? "" : item.progressSource === "comment-analysis" ? " (llm)" : " (est)";
20782
- 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
+ );
20783
20820
  if (item.commentSummary) {
20784
20821
  parts.push(`${indent} \u{1F4AC} ${item.commentSummary}`);
20785
20822
  }
@@ -20789,7 +20826,9 @@ function formatItemLine(parts, item, depth) {
20789
20826
  const doneMarker = link.isDone ? " \u2713" : "";
20790
20827
  const blockerResolved = link.isDone && BLOCKER_LINK_PATTERNS.some((p) => link.relationship.toLowerCase().includes(p.split(" ")[0])) ? " unblock signal" : "";
20791
20828
  const wontDo = WONT_DO_STATUSES.has(link.status.toLowerCase()) ? " \u26A0 needs review" : "";
20792
- 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
+ );
20793
20832
  const signal = item.linkedIssueSignals.find((s) => s.sourceKey === link.key);
20794
20833
  if (signal?.commentSummary) {
20795
20834
  parts.push(`${indent} \u{1F4AC} ${signal.commentSummary}`);
@@ -20808,6 +20847,7 @@ function progressBar(pct) {
20808
20847
  var MAX_ARTIFACT_NODES = 50;
20809
20848
  var MAX_LLM_DEPTH = 3;
20810
20849
  var MAX_LLM_COMMENT_CHARS = 8e3;
20850
+ var DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD = 15;
20811
20851
  async function assessArtifact(store, client, host, options) {
20812
20852
  const visited = /* @__PURE__ */ new Set();
20813
20853
  return _assessArtifactRecursive(store, client, host, options, visited, 0);
@@ -20815,10 +20855,14 @@ async function assessArtifact(store, client, host, options) {
20815
20855
  async function _assessArtifactRecursive(store, client, host, options, visited, depth) {
20816
20856
  const errors = [];
20817
20857
  if (visited.has(options.artifactId)) {
20818
- return emptyArtifactReport(options.artifactId, [`Cycle detected: ${options.artifactId} already visited`]);
20858
+ return emptyArtifactReport(options.artifactId, [
20859
+ `Cycle detected: ${options.artifactId} already visited`
20860
+ ]);
20819
20861
  }
20820
20862
  if (visited.size >= MAX_ARTIFACT_NODES) {
20821
- 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
+ ]);
20822
20866
  }
20823
20867
  visited.add(options.artifactId);
20824
20868
  const doc = store.get(options.artifactId);
@@ -20888,27 +20932,29 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
20888
20932
  if (result.status === "fulfilled") {
20889
20933
  const { key, issue: li, comments: lc } = result.value;
20890
20934
  linkedJiraIssues.set(key, { issue: li, comments: lc });
20891
- 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
+ );
20892
20938
  for (const nl of newLinks) {
20893
20939
  jiraVisited.add(nl.key);
20894
20940
  queue.push(nl.key);
20895
20941
  }
20896
20942
  } else {
20897
20943
  const batchKey = batch[results.indexOf(result)];
20898
- 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
+ );
20899
20947
  }
20900
20948
  }
20901
20949
  }
20902
- const { allLinks, allSignals } = collectTransitiveLinks(
20903
- issue2,
20904
- jiraIssues,
20905
- linkedJiraIssues
20906
- );
20950
+ const { allLinks, allSignals } = collectTransitiveLinks(issue2, jiraIssues, linkedJiraIssues);
20907
20951
  linkedIssues = allLinks;
20908
20952
  linkedIssueSignals = allSignals;
20909
20953
  analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
20910
20954
  } catch (err) {
20911
- 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
+ );
20912
20958
  }
20913
20959
  }
20914
20960
  const currentProgress = getEffectiveProgress(fm);
@@ -20949,7 +20995,11 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
20949
20995
  const primaryHasComments = jiraKey ? (jiraIssues.get(jiraKey)?.comments.length ?? 0) > 0 : false;
20950
20996
  let commentAnalysisProgress = null;
20951
20997
  if (depth < MAX_LLM_DEPTH && jiraKey && primaryHasComments) {
20952
- const estimatedChars = estimateCommentTextSize(jiraIssues, linkedJiraIssues, linkedIssueSignals);
20998
+ const estimatedChars = estimateCommentTextSize(
20999
+ jiraIssues,
21000
+ linkedJiraIssues,
21001
+ linkedIssueSignals
21002
+ );
20953
21003
  if (estimatedChars <= MAX_LLM_COMMENT_CHARS) {
20954
21004
  try {
20955
21005
  const analysis = await analyzeSingleArtifactComments(
@@ -20964,14 +21014,16 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
20964
21014
  commentSummary = analysis.summary;
20965
21015
  commentAnalysisProgress = analysis.progressEstimate;
20966
21016
  if (commentAnalysisProgress !== null) {
20967
- const hasExplicitProgress = "progress" in fm && typeof fm.progress === "number";
20968
- 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" : "";
20969
21021
  proposedUpdates.push({
20970
21022
  artifactId: fm.id,
20971
21023
  field: "progress",
20972
21024
  currentValue: currentProgress,
20973
21025
  proposedValue: commentAnalysisProgress,
20974
- reason: `Comment analysis estimates ${commentAnalysisProgress}% progress`
21026
+ reason: `Comment-derived estimate (${commentAnalysisProgress}%) diverges from current (${currentProgress}%) by ${divergence}pp${overrideWarning}`
20975
21027
  });
20976
21028
  }
20977
21029
  }
@@ -20984,7 +21036,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
20984
21036
  const children = [];
20985
21037
  for (const childId of childIds) {
20986
21038
  if (visited.size >= MAX_ARTIFACT_NODES) {
20987
- 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
+ );
20988
21042
  break;
20989
21043
  }
20990
21044
  const childReport = await _assessArtifactRecursive(
@@ -21019,6 +21073,42 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
21019
21073
  });
21020
21074
  }
21021
21075
  }
21076
+ const prerequisiteWeight = options.prerequisiteWeight ?? 0.3;
21077
+ const blockerResult = computeBlockerProgress(linkedIssues, prerequisiteWeight);
21078
+ let blockerProgressValue = null;
21079
+ let totalBlockersCount = 0;
21080
+ let resolvedBlockersCount = 0;
21081
+ if (blockerResult && !fm.progressOverride && !DONE_STATUSES6.has(fm.status)) {
21082
+ blockerProgressValue = blockerResult.blockerProgress;
21083
+ totalBlockersCount = blockerResult.totalBlockers;
21084
+ resolvedBlockersCount = blockerResult.resolvedBlockers;
21085
+ const lastProgressUpdate = findLast(
21086
+ proposedUpdates,
21087
+ (u) => u.artifactId === fm.id && u.field === "progress"
21088
+ );
21089
+ const implementationProgress = lastProgressUpdate ? lastProgressUpdate.proposedValue : currentProgress;
21090
+ const combinedProgress = Math.round(
21091
+ blockerResult.blockerProgress + implementationProgress * (1 - prerequisiteWeight)
21092
+ );
21093
+ const estimatedProgress = Math.max(currentProgress, combinedProgress);
21094
+ if (estimatedProgress !== currentProgress && estimatedProgress !== implementationProgress) {
21095
+ for (let i = proposedUpdates.length - 1; i >= 0; i--) {
21096
+ if (proposedUpdates[i].artifactId === fm.id && proposedUpdates[i].field === "progress") {
21097
+ proposedUpdates.splice(i, 1);
21098
+ }
21099
+ }
21100
+ proposedUpdates.push({
21101
+ artifactId: fm.id,
21102
+ field: "progress",
21103
+ currentValue: currentProgress,
21104
+ proposedValue: estimatedProgress,
21105
+ reason: `Blocker resolution (${resolvedBlockersCount}/${totalBlockersCount}) + implementation \u2192 dependency-weighted progress ${estimatedProgress}%`
21106
+ });
21107
+ }
21108
+ } else if (blockerResult) {
21109
+ totalBlockersCount = blockerResult.totalBlockers;
21110
+ resolvedBlockersCount = blockerResult.resolvedBlockers;
21111
+ }
21022
21112
  const signals = buildSignals(commentSignals, linkedIssues, statusDrift, proposedMarvinStatus);
21023
21113
  const appliedUpdates = [];
21024
21114
  if (options.applyUpdates && proposedUpdates.length > 0) {
@@ -21061,7 +21151,10 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
21061
21151
  commentAnalysisProgress,
21062
21152
  signals,
21063
21153
  children,
21064
- linkedIssues
21154
+ linkedIssues,
21155
+ blockerProgressValue,
21156
+ totalBlockersCount,
21157
+ resolvedBlockersCount
21065
21158
  );
21066
21159
  const existingHistory = Array.isArray(fm.assessmentHistory) ? fm.assessmentHistory : [];
21067
21160
  const legacySummary = fm.assessmentSummary;
@@ -21087,7 +21180,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
21087
21180
  }
21088
21181
  store.update(fm.id, payload);
21089
21182
  } catch (err) {
21090
- 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
+ );
21091
21186
  }
21092
21187
  }
21093
21188
  return {
@@ -21110,6 +21205,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
21110
21205
  commentAnalysisProgress,
21111
21206
  linkedIssues,
21112
21207
  linkedIssueSignals,
21208
+ blockerProgress: blockerProgressValue,
21209
+ totalBlockers: totalBlockersCount,
21210
+ resolvedBlockers: resolvedBlockersCount,
21113
21211
  children,
21114
21212
  proposedUpdates: options.applyUpdates ? [] : proposedUpdates,
21115
21213
  appliedUpdates,
@@ -21144,9 +21242,7 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
21144
21242
  signals.push(`\u{1F6AB} Blocker \u2014 "${s.snippet}"`);
21145
21243
  }
21146
21244
  }
21147
- const blockingLinks = linkedIssues.filter(
21148
- (l) => l.relationship.toLowerCase().includes("block")
21149
- );
21245
+ const blockingLinks = linkedIssues.filter((l) => l.relationship.toLowerCase().includes("block"));
21150
21246
  const activeBlockers = blockingLinks.filter((l) => !l.isDone);
21151
21247
  const resolvedBlockers = blockingLinks.filter((l) => l.isDone);
21152
21248
  if (activeBlockers.length > 0) {
@@ -21155,7 +21251,9 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
21155
21251
  }
21156
21252
  }
21157
21253
  if (resolvedBlockers.length > 0 && activeBlockers.length === 0) {
21158
- 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
+ );
21159
21257
  }
21160
21258
  const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
21161
21259
  for (const l of wontDoLinks) {
@@ -21217,9 +21315,11 @@ async function analyzeSingleArtifactComments(artifactId, title, jiraKey, jiraSta
21217
21315
  const text = extractCommentText(c.body);
21218
21316
  return `[${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
21219
21317
  }).join("\n");
21220
- promptParts.push(`## ${artifactId} \u2014 ${title} (${jiraKey}, status: ${jiraStatus})
21318
+ promptParts.push(
21319
+ `## ${artifactId} \u2014 ${title} (${jiraKey}, status: ${jiraStatus})
21221
21320
  Comments:
21222
- ${commentTexts}`);
21321
+ ${commentTexts}`
21322
+ );
21223
21323
  }
21224
21324
  for (const signal of linkedIssueSignals) {
21225
21325
  const linkedData = linkedJiraIssues.get(signal.sourceKey);
@@ -21289,6 +21389,9 @@ function emptyArtifactReport(artifactId, errors) {
21289
21389
  commentAnalysisProgress: null,
21290
21390
  linkedIssues: [],
21291
21391
  linkedIssueSignals: [],
21392
+ blockerProgress: null,
21393
+ totalBlockers: 0,
21394
+ resolvedBlockers: 0,
21292
21395
  children: [],
21293
21396
  proposedUpdates: [],
21294
21397
  appliedUpdates: [],
@@ -21296,7 +21399,7 @@ function emptyArtifactReport(artifactId, errors) {
21296
21399
  errors
21297
21400
  };
21298
21401
  }
21299
- function buildAssessmentSummary(commentSummary, commentAnalysisProgress, signals, children, linkedIssues) {
21402
+ function buildAssessmentSummary(commentSummary, commentAnalysisProgress, signals, children, linkedIssues, blockerProgress = null, totalBlockers = 0, resolvedBlockers = 0) {
21300
21403
  const childProgressValues = children.map((c) => {
21301
21404
  const updates = c.appliedUpdates.length > 0 ? c.appliedUpdates : c.proposedUpdates;
21302
21405
  const lastStatus = findLast(updates, (u) => u.field === "status");
@@ -21305,7 +21408,7 @@ function buildAssessmentSummary(commentSummary, commentAnalysisProgress, signals
21305
21408
  if (lastProgress) return lastProgress.proposedValue;
21306
21409
  return c.marvinProgress;
21307
21410
  });
21308
- const childDoneCount = children.filter((c, i) => {
21411
+ const childDoneCount = children.filter((c) => {
21309
21412
  const updates = c.appliedUpdates.length > 0 ? c.appliedUpdates : c.proposedUpdates;
21310
21413
  const lastStatus = findLast(updates, (u) => u.field === "status");
21311
21414
  const effectiveStatus = lastStatus ? String(lastStatus.proposedValue) : c.marvinStatus;
@@ -21320,7 +21423,10 @@ function buildAssessmentSummary(commentSummary, commentAnalysisProgress, signals
21320
21423
  childCount: children.length,
21321
21424
  childDoneCount,
21322
21425
  childRollupProgress,
21323
- linkedIssueCount: linkedIssues.length
21426
+ linkedIssueCount: linkedIssues.length,
21427
+ blockerProgress,
21428
+ totalBlockers,
21429
+ resolvedBlockers
21324
21430
  };
21325
21431
  }
21326
21432
  function formatArtifactReport(report) {
@@ -21338,7 +21444,8 @@ function formatArtifactReport(report) {
21338
21444
  parts.push(`## Jira State (${report.jiraKey})`);
21339
21445
  const jiraParts = [`Status: ${report.jiraStatus ?? "unknown"}`];
21340
21446
  if (report.jiraAssignee) jiraParts.push(`Assignee: ${report.jiraAssignee}`);
21341
- if (report.jiraSubtaskProgress !== null) jiraParts.push(`Subtask progress: ${report.jiraSubtaskProgress}%`);
21447
+ if (report.jiraSubtaskProgress !== null)
21448
+ jiraParts.push(`Subtask progress: ${report.jiraSubtaskProgress}%`);
21342
21449
  parts.push(jiraParts.join(" | "));
21343
21450
  if (report.statusDrift) {
21344
21451
  parts.push(`\u26A0 Drift: ${report.marvinStatus} \u2192 ${report.proposedMarvinStatus}`);
@@ -21356,13 +21463,23 @@ function formatArtifactReport(report) {
21356
21463
  }
21357
21464
  parts.push("");
21358
21465
  }
21466
+ if (report.totalBlockers > 0) {
21467
+ parts.push(`## Blocker Resolution`);
21468
+ const bpLabel = report.blockerProgress !== null ? `${report.blockerProgress}%` : "n/a (skipped)";
21469
+ parts.push(
21470
+ ` ${report.resolvedBlockers}/${report.totalBlockers} blockers resolved \u2192 ${bpLabel} prerequisite progress`
21471
+ );
21472
+ parts.push("");
21473
+ }
21359
21474
  if (report.children.length > 0) {
21360
21475
  const doneCount = report.children.filter((c) => DONE_STATUSES6.has(c.marvinStatus)).length;
21361
21476
  const childProgress = Math.round(
21362
21477
  report.children.reduce((s, c) => s + c.marvinProgress, 0) / report.children.length
21363
21478
  );
21364
21479
  const bar = progressBar(childProgress);
21365
- parts.push(`## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`);
21480
+ parts.push(
21481
+ `## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`
21482
+ );
21366
21483
  for (const child of report.children) {
21367
21484
  formatArtifactChild(parts, child, 1);
21368
21485
  }
@@ -21372,7 +21489,9 @@ function formatArtifactReport(report) {
21372
21489
  parts.push(`## Linked Issues (${report.linkedIssues.length})`);
21373
21490
  for (const link of report.linkedIssues) {
21374
21491
  const doneMarker = link.isDone ? " \u2713" : "";
21375
- 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
+ );
21376
21495
  const signal = report.linkedIssueSignals.find((s) => s.sourceKey === link.key);
21377
21496
  if (signal?.commentSummary) {
21378
21497
  parts.push(` \u{1F4AC} ${signal.commentSummary}`);
@@ -21390,7 +21509,9 @@ function formatArtifactReport(report) {
21390
21509
  if (report.proposedUpdates.length > 0) {
21391
21510
  parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
21392
21511
  for (const update of report.proposedUpdates) {
21393
- 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
+ );
21394
21515
  parts.push(` Reason: ${update.reason}`);
21395
21516
  }
21396
21517
  parts.push("");
@@ -21400,7 +21521,9 @@ function formatArtifactReport(report) {
21400
21521
  if (report.appliedUpdates.length > 0) {
21401
21522
  parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
21402
21523
  for (const update of report.appliedUpdates) {
21403
- 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
+ );
21404
21527
  }
21405
21528
  parts.push("");
21406
21529
  }
@@ -21423,7 +21546,9 @@ function formatArtifactChild(parts, child, depth) {
21423
21546
  if (s.startsWith("\u2705 No active")) continue;
21424
21547
  signalHints.push(s);
21425
21548
  }
21426
- 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
+ );
21427
21552
  if (child.commentSummary) {
21428
21553
  parts.push(`${indent} \u{1F4AC} ${child.commentSummary}`);
21429
21554
  }
@@ -22254,10 +22379,12 @@ function createJiraTools(store, projectConfig) {
22254
22379
  // --- Single-artifact assessment ---
22255
22380
  tool20(
22256
22381
  "assess_artifact",
22257
- "Deep assessment of a single Marvin artifact (task, action, or epic). Fetches live Jira status, analyzes comments with LLM, traverses all linked issues, detects drift, rolls up child progress, and extracts contextual signals (blockers, unblocks, handoffs, superseded work).",
22382
+ "Deep assessment of a single Marvin artifact (task, action, or epic). Fetches live Jira status, analyzes comments with LLM, traverses all linked issues, detects drift, rolls up child progress, computes dependency-weighted progress from blocker resolution, and extracts contextual signals (blockers, unblocks, handoffs, superseded work).",
22258
22383
  {
22259
22384
  artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'T-063', 'A-151', 'E-003')"),
22260
- applyUpdates: external_exports.boolean().optional().describe("Apply proposed status/progress updates to the artifact (default false)")
22385
+ applyUpdates: external_exports.boolean().optional().describe("Apply proposed status/progress updates to the artifact (default false)"),
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).")
22261
22388
  },
22262
22389
  async (args) => {
22263
22390
  const jira = createJiraClient(jiraUserConfig);
@@ -22269,6 +22396,8 @@ function createJiraTools(store, projectConfig) {
22269
22396
  {
22270
22397
  artifactId: args.artifactId,
22271
22398
  applyUpdates: args.applyUpdates ?? false,
22399
+ prerequisiteWeight: args.prerequisiteWeight,
22400
+ progressDivergenceThreshold: args.progressDivergenceThreshold,
22272
22401
  statusMap
22273
22402
  }
22274
22403
  );
@@ -25907,7 +26036,10 @@ function normalizeEntry(entry) {
25907
26036
  childCount: typeof entry.childCount === "number" ? entry.childCount : 0,
25908
26037
  childDoneCount: typeof entry.childDoneCount === "number" ? entry.childDoneCount : 0,
25909
26038
  childRollupProgress: typeof entry.childRollupProgress === "number" ? entry.childRollupProgress : null,
25910
- linkedIssueCount: typeof entry.linkedIssueCount === "number" ? entry.linkedIssueCount : 0
26039
+ linkedIssueCount: typeof entry.linkedIssueCount === "number" ? entry.linkedIssueCount : 0,
26040
+ blockerProgress: typeof entry.blockerProgress === "number" ? entry.blockerProgress : null,
26041
+ totalBlockers: typeof entry.totalBlockers === "number" ? entry.totalBlockers : 0,
26042
+ resolvedBlockers: typeof entry.resolvedBlockers === "number" ? entry.resolvedBlockers : 0
25911
26043
  };
25912
26044
  }
25913
26045
  function renderAssessmentTimeline(history) {
@@ -25927,6 +26059,10 @@ function renderAssessmentTimeline(history) {
25927
26059
  const bar = progressBarHtml(entry.childRollupProgress ?? 0);
25928
26060
  parts.push(`<div class="assessment-stat">\u{1F476} Children: ${entry.childDoneCount}/${entry.childCount} done ${bar} ${entry.childRollupProgress ?? 0}%</div>`);
25929
26061
  }
26062
+ if (entry.totalBlockers > 0) {
26063
+ const bar = progressBarHtml(entry.blockerProgress ?? 0);
26064
+ parts.push(`<div class="assessment-stat">\u{1F6A7} Blockers: ${entry.resolvedBlockers}/${entry.totalBlockers} resolved ${bar} ${entry.blockerProgress ?? 0}%</div>`);
26065
+ }
25930
26066
  if (entry.linkedIssueCount > 0) {
25931
26067
  parts.push(`<div class="assessment-stat">\u{1F517} Linked issues: ${entry.linkedIssueCount}</div>`);
25932
26068
  }