mrvn-cli 0.5.14 → 0.5.15

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.
@@ -19774,6 +19774,61 @@ function generateProposedActions(issues) {
19774
19774
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
19775
19775
  var DONE_STATUSES7 = /* @__PURE__ */ new Set(["done", "closed", "resolved", "obsolete", "wont do", "cancelled"]);
19776
19776
  var BATCH_SIZE = 5;
19777
+ var BLOCKED_WEIGHT_RISK_THRESHOLD = 0.3;
19778
+ var COMPLEXITY_WEIGHTS = {
19779
+ trivial: 1,
19780
+ simple: 2,
19781
+ moderate: 3,
19782
+ complex: 5,
19783
+ "very-complex": 8
19784
+ };
19785
+ var DEFAULT_WEIGHT = 3;
19786
+ var STATUS_PROGRESS_DEFAULTS = {
19787
+ done: 100,
19788
+ closed: 100,
19789
+ resolved: 100,
19790
+ obsolete: 100,
19791
+ "wont do": 100,
19792
+ cancelled: 100,
19793
+ review: 80,
19794
+ "in-progress": 40,
19795
+ ready: 5,
19796
+ backlog: 0,
19797
+ open: 0
19798
+ };
19799
+ var BLOCKED_DEFAULT_PROGRESS = 10;
19800
+ function resolveWeight(complexity) {
19801
+ if (complexity && complexity in COMPLEXITY_WEIGHTS) {
19802
+ return { weight: COMPLEXITY_WEIGHTS[complexity], weightSource: "complexity" };
19803
+ }
19804
+ return { weight: DEFAULT_WEIGHT, weightSource: "default" };
19805
+ }
19806
+ function resolveProgress(frontmatter, commentAnalysisProgress) {
19807
+ const hasExplicitProgress = "progress" in frontmatter && typeof frontmatter.progress === "number";
19808
+ if (hasExplicitProgress) {
19809
+ return { progress: Math.max(0, Math.min(100, Math.round(frontmatter.progress))), progressSource: "explicit" };
19810
+ }
19811
+ if (commentAnalysisProgress !== null) {
19812
+ return { progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))), progressSource: "comment-analysis" };
19813
+ }
19814
+ const status = frontmatter.status;
19815
+ if (status === "blocked") {
19816
+ return { progress: BLOCKED_DEFAULT_PROGRESS, progressSource: "status-default" };
19817
+ }
19818
+ const defaultProgress = STATUS_PROGRESS_DEFAULTS[status] ?? 0;
19819
+ return { progress: defaultProgress, progressSource: "status-default" };
19820
+ }
19821
+ function computeWeightedProgress(items) {
19822
+ if (items.length === 0) return 0;
19823
+ let totalWeight = 0;
19824
+ let weightedSum = 0;
19825
+ for (const item of items) {
19826
+ totalWeight += item.weight;
19827
+ weightedSum += item.weight * item.progress;
19828
+ }
19829
+ if (totalWeight === 0) return 0;
19830
+ return Math.round(weightedSum / totalWeight);
19831
+ }
19777
19832
  async function assessSprintProgress(store, client, host, options = {}) {
19778
19833
  const errors = [];
19779
19834
  const sprintData = collectSprintSummaryData(store, options.sprintId);
@@ -19885,12 +19940,18 @@ async function assessSprintProgress(store, client, host, options = {}) {
19885
19940
  }
19886
19941
  const tags = fm.tags ?? [];
19887
19942
  const focusTag = tags.find((t) => t.startsWith("focus:"));
19943
+ const { weight, weightSource } = resolveWeight(fm.complexity);
19944
+ const { progress: resolvedProgress, progressSource } = resolveProgress(fm, null);
19888
19945
  const report = {
19889
19946
  id: fm.id,
19890
19947
  title: fm.title,
19891
19948
  type: fm.type,
19892
19949
  marvinStatus: fm.status,
19893
19950
  marvinProgress: currentProgress,
19951
+ progress: resolvedProgress,
19952
+ progressSource,
19953
+ weight,
19954
+ weightSource,
19894
19955
  jiraKey,
19895
19956
  jiraStatus,
19896
19957
  jiraSubtaskProgress,
@@ -19923,32 +19984,44 @@ async function assessSprintProgress(store, client, host, options = {}) {
19923
19984
  for (const child of children) childIds.add(child.id);
19924
19985
  }
19925
19986
  const rootReports = itemReports.filter((r) => !childIds.has(r.id));
19987
+ for (const report of rootReports) {
19988
+ if (report.children.length > 0) {
19989
+ const doc = store.get(report.id);
19990
+ const hasExplicitOverride = doc?.frontmatter.progressOverride;
19991
+ if (!hasExplicitOverride) {
19992
+ report.progress = computeWeightedProgress(report.children);
19993
+ report.progressSource = "status-default";
19994
+ }
19995
+ }
19996
+ }
19926
19997
  const focusAreaMap = /* @__PURE__ */ new Map();
19927
19998
  for (const report of rootReports) {
19928
- const area = report.focusArea ?? "Uncategorized";
19929
- if (!focusAreaMap.has(area)) focusAreaMap.set(area, []);
19930
- focusAreaMap.get(area).push(report);
19999
+ if (!report.focusArea) continue;
20000
+ if (!focusAreaMap.has(report.focusArea)) focusAreaMap.set(report.focusArea, []);
20001
+ focusAreaMap.get(report.focusArea).push(report);
19931
20002
  }
19932
20003
  const focusAreas = [];
19933
20004
  for (const [name, items] of focusAreaMap) {
19934
20005
  const allFlatItems = items.flatMap((i) => [i, ...i.children]);
19935
20006
  const doneCount = allFlatItems.filter((i) => DONE_STATUSES7.has(i.marvinStatus)).length;
19936
20007
  const blockedCount = allFlatItems.filter((i) => i.marvinStatus === "blocked").length;
19937
- const avgProgress = allFlatItems.length > 0 ? Math.round(allFlatItems.reduce((s, i) => s + i.marvinProgress, 0) / allFlatItems.length) : 0;
20008
+ const progress = computeWeightedProgress(items);
20009
+ const totalWeight = items.reduce((s, i) => s + i.weight, 0);
20010
+ const blockedWeight = items.filter((i) => i.marvinStatus === "blocked").reduce((s, i) => s + i.weight, 0);
20011
+ const blockedWeightPct = totalWeight > 0 ? Math.round(blockedWeight / totalWeight * 100) : 0;
20012
+ const riskWarning = blockedWeightPct > BLOCKED_WEIGHT_RISK_THRESHOLD * 100 ? `${blockedWeightPct}% of scope is blocked` : null;
19938
20013
  focusAreas.push({
19939
20014
  name,
19940
- items,
19941
- totalCount: allFlatItems.length,
20015
+ progress,
20016
+ taskCount: allFlatItems.length,
19942
20017
  doneCount,
19943
20018
  blockedCount,
19944
- avgProgress
20019
+ blockedWeightPct,
20020
+ riskWarning,
20021
+ items
19945
20022
  });
19946
20023
  }
19947
- focusAreas.sort((a, b) => {
19948
- if (a.name === "Uncategorized") return 1;
19949
- if (b.name === "Uncategorized") return -1;
19950
- return a.name.localeCompare(b.name);
19951
- });
20024
+ focusAreas.sort((a, b) => a.name.localeCompare(b.name));
19952
20025
  const driftItems = itemReports.filter((r) => r.statusDrift || r.progressDrift);
19953
20026
  const blockers = itemReports.filter(
19954
20027
  (r) => r.marvinStatus === "blocked" || r.commentSignals.some((s) => s.type === "blocker")
@@ -19964,7 +20037,19 @@ async function assessSprintProgress(store, client, host, options = {}) {
19964
20037
  );
19965
20038
  for (const [artifactId, summary] of summaries) {
19966
20039
  const report = itemReports.find((r) => r.id === artifactId);
19967
- if (report) report.commentSummary = summary;
20040
+ if (report) {
20041
+ report.commentSummary = summary;
20042
+ if (report.progressSource === "status-default") {
20043
+ const pctMatch = summary.match(/(\d{1,3})%/);
20044
+ if (pctMatch) {
20045
+ const pct = parseInt(pctMatch[1], 10);
20046
+ if (pct >= 0 && pct <= 100) {
20047
+ report.progress = pct;
20048
+ report.progressSource = "comment-analysis";
20049
+ }
20050
+ }
20051
+ }
20052
+ }
19968
20053
  }
19969
20054
  } catch (err) {
19970
20055
  errors.push(`Comment analysis failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -20006,7 +20091,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
20006
20091
  totalDays: sprintData.timeline.totalDays,
20007
20092
  percentComplete: sprintData.timeline.percentComplete
20008
20093
  },
20009
- overallProgress: sprintData.workItems.completionPct,
20094
+ overallProgress: rootReports.length > 0 ? computeWeightedProgress(rootReports) : sprintData.workItems.completionPct,
20010
20095
  itemReports: rootReports,
20011
20096
  focusAreas,
20012
20097
  driftItems,
@@ -20103,9 +20188,12 @@ function formatProgressReport(report) {
20103
20188
  parts.push(`## Focus Areas`);
20104
20189
  parts.push("");
20105
20190
  for (const area of report.focusAreas) {
20106
- const bar = progressBar(area.avgProgress);
20107
- parts.push(`### ${area.name} ${bar} ${area.avgProgress}%`);
20108
- parts.push(`${area.doneCount}/${area.totalCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`);
20191
+ const bar = progressBar(area.progress);
20192
+ parts.push(`### ${area.name} ${bar} ${area.progress}%`);
20193
+ parts.push(`${area.doneCount}/${area.taskCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`);
20194
+ if (area.riskWarning) {
20195
+ parts.push(` \u26A0 ${area.riskWarning}`);
20196
+ }
20109
20197
  parts.push("");
20110
20198
  for (const item of area.items) {
20111
20199
  formatItemLine(parts, item, 0);
@@ -20169,8 +20257,10 @@ function formatItemLine(parts, item, depth) {
20169
20257
  const statusIcon = DONE_STATUSES7.has(item.marvinStatus) ? "\u2713" : item.marvinStatus === "blocked" ? "\u{1F6AB}" : item.marvinStatus === "in-progress" ? "\u25B6" : "\u25CB";
20170
20258
  const jiraLabel = item.jiraKey ? ` [${item.jiraKey}: ${item.jiraStatus}]` : "";
20171
20259
  const driftFlag = item.statusDrift ? " \u26A0drift" : "";
20172
- const progressLabel = item.marvinProgress > 0 ? ` ${item.marvinProgress}%` : "";
20173
- parts.push(`${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${jiraLabel}${driftFlag}`);
20260
+ const progressLabel = ` ${item.progress}%`;
20261
+ const weightLabel = `w${item.weight}`;
20262
+ const sourceLabel = item.progressSource === "explicit" ? "" : item.progressSource === "comment-analysis" ? " (llm)" : " (est)";
20263
+ parts.push(`${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${sourceLabel} (${weightLabel})${jiraLabel}${driftFlag}`);
20174
20264
  if (item.commentSummary) {
20175
20265
  parts.push(`${indent} \u{1F4AC} ${item.commentSummary}`);
20176
20266
  }