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.
package/dist/index.js CHANGED
@@ -25728,6 +25728,61 @@ function generateProposedActions(issues) {
25728
25728
  import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
25729
25729
  var DONE_STATUSES16 = /* @__PURE__ */ new Set(["done", "closed", "resolved", "obsolete", "wont do", "cancelled"]);
25730
25730
  var BATCH_SIZE = 5;
25731
+ var BLOCKED_WEIGHT_RISK_THRESHOLD = 0.3;
25732
+ var COMPLEXITY_WEIGHTS = {
25733
+ trivial: 1,
25734
+ simple: 2,
25735
+ moderate: 3,
25736
+ complex: 5,
25737
+ "very-complex": 8
25738
+ };
25739
+ var DEFAULT_WEIGHT = 3;
25740
+ var STATUS_PROGRESS_DEFAULTS = {
25741
+ done: 100,
25742
+ closed: 100,
25743
+ resolved: 100,
25744
+ obsolete: 100,
25745
+ "wont do": 100,
25746
+ cancelled: 100,
25747
+ review: 80,
25748
+ "in-progress": 40,
25749
+ ready: 5,
25750
+ backlog: 0,
25751
+ open: 0
25752
+ };
25753
+ var BLOCKED_DEFAULT_PROGRESS = 10;
25754
+ function resolveWeight(complexity) {
25755
+ if (complexity && complexity in COMPLEXITY_WEIGHTS) {
25756
+ return { weight: COMPLEXITY_WEIGHTS[complexity], weightSource: "complexity" };
25757
+ }
25758
+ return { weight: DEFAULT_WEIGHT, weightSource: "default" };
25759
+ }
25760
+ function resolveProgress(frontmatter, commentAnalysisProgress) {
25761
+ const hasExplicitProgress = "progress" in frontmatter && typeof frontmatter.progress === "number";
25762
+ if (hasExplicitProgress) {
25763
+ return { progress: Math.max(0, Math.min(100, Math.round(frontmatter.progress))), progressSource: "explicit" };
25764
+ }
25765
+ if (commentAnalysisProgress !== null) {
25766
+ return { progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))), progressSource: "comment-analysis" };
25767
+ }
25768
+ const status = frontmatter.status;
25769
+ if (status === "blocked") {
25770
+ return { progress: BLOCKED_DEFAULT_PROGRESS, progressSource: "status-default" };
25771
+ }
25772
+ const defaultProgress = STATUS_PROGRESS_DEFAULTS[status] ?? 0;
25773
+ return { progress: defaultProgress, progressSource: "status-default" };
25774
+ }
25775
+ function computeWeightedProgress(items) {
25776
+ if (items.length === 0) return 0;
25777
+ let totalWeight = 0;
25778
+ let weightedSum = 0;
25779
+ for (const item of items) {
25780
+ totalWeight += item.weight;
25781
+ weightedSum += item.weight * item.progress;
25782
+ }
25783
+ if (totalWeight === 0) return 0;
25784
+ return Math.round(weightedSum / totalWeight);
25785
+ }
25731
25786
  async function assessSprintProgress(store, client, host, options = {}) {
25732
25787
  const errors = [];
25733
25788
  const sprintData = collectSprintSummaryData(store, options.sprintId);
@@ -25839,12 +25894,18 @@ async function assessSprintProgress(store, client, host, options = {}) {
25839
25894
  }
25840
25895
  const tags = fm.tags ?? [];
25841
25896
  const focusTag = tags.find((t) => t.startsWith("focus:"));
25897
+ const { weight, weightSource } = resolveWeight(fm.complexity);
25898
+ const { progress: resolvedProgress, progressSource } = resolveProgress(fm, null);
25842
25899
  const report = {
25843
25900
  id: fm.id,
25844
25901
  title: fm.title,
25845
25902
  type: fm.type,
25846
25903
  marvinStatus: fm.status,
25847
25904
  marvinProgress: currentProgress,
25905
+ progress: resolvedProgress,
25906
+ progressSource,
25907
+ weight,
25908
+ weightSource,
25848
25909
  jiraKey,
25849
25910
  jiraStatus,
25850
25911
  jiraSubtaskProgress,
@@ -25877,32 +25938,44 @@ async function assessSprintProgress(store, client, host, options = {}) {
25877
25938
  for (const child of children) childIds.add(child.id);
25878
25939
  }
25879
25940
  const rootReports = itemReports.filter((r) => !childIds.has(r.id));
25941
+ for (const report of rootReports) {
25942
+ if (report.children.length > 0) {
25943
+ const doc = store.get(report.id);
25944
+ const hasExplicitOverride = doc?.frontmatter.progressOverride;
25945
+ if (!hasExplicitOverride) {
25946
+ report.progress = computeWeightedProgress(report.children);
25947
+ report.progressSource = "status-default";
25948
+ }
25949
+ }
25950
+ }
25880
25951
  const focusAreaMap = /* @__PURE__ */ new Map();
25881
25952
  for (const report of rootReports) {
25882
- const area = report.focusArea ?? "Uncategorized";
25883
- if (!focusAreaMap.has(area)) focusAreaMap.set(area, []);
25884
- focusAreaMap.get(area).push(report);
25953
+ if (!report.focusArea) continue;
25954
+ if (!focusAreaMap.has(report.focusArea)) focusAreaMap.set(report.focusArea, []);
25955
+ focusAreaMap.get(report.focusArea).push(report);
25885
25956
  }
25886
25957
  const focusAreas = [];
25887
25958
  for (const [name, items] of focusAreaMap) {
25888
25959
  const allFlatItems = items.flatMap((i) => [i, ...i.children]);
25889
25960
  const doneCount = allFlatItems.filter((i) => DONE_STATUSES16.has(i.marvinStatus)).length;
25890
25961
  const blockedCount = allFlatItems.filter((i) => i.marvinStatus === "blocked").length;
25891
- const avgProgress = allFlatItems.length > 0 ? Math.round(allFlatItems.reduce((s, i) => s + i.marvinProgress, 0) / allFlatItems.length) : 0;
25962
+ const progress = computeWeightedProgress(items);
25963
+ const totalWeight = items.reduce((s, i) => s + i.weight, 0);
25964
+ const blockedWeight = items.filter((i) => i.marvinStatus === "blocked").reduce((s, i) => s + i.weight, 0);
25965
+ const blockedWeightPct = totalWeight > 0 ? Math.round(blockedWeight / totalWeight * 100) : 0;
25966
+ const riskWarning = blockedWeightPct > BLOCKED_WEIGHT_RISK_THRESHOLD * 100 ? `${blockedWeightPct}% of scope is blocked` : null;
25892
25967
  focusAreas.push({
25893
25968
  name,
25894
- items,
25895
- totalCount: allFlatItems.length,
25969
+ progress,
25970
+ taskCount: allFlatItems.length,
25896
25971
  doneCount,
25897
25972
  blockedCount,
25898
- avgProgress
25973
+ blockedWeightPct,
25974
+ riskWarning,
25975
+ items
25899
25976
  });
25900
25977
  }
25901
- focusAreas.sort((a, b) => {
25902
- if (a.name === "Uncategorized") return 1;
25903
- if (b.name === "Uncategorized") return -1;
25904
- return a.name.localeCompare(b.name);
25905
- });
25978
+ focusAreas.sort((a, b) => a.name.localeCompare(b.name));
25906
25979
  const driftItems = itemReports.filter((r) => r.statusDrift || r.progressDrift);
25907
25980
  const blockers = itemReports.filter(
25908
25981
  (r) => r.marvinStatus === "blocked" || r.commentSignals.some((s) => s.type === "blocker")
@@ -25918,7 +25991,19 @@ async function assessSprintProgress(store, client, host, options = {}) {
25918
25991
  );
25919
25992
  for (const [artifactId, summary] of summaries) {
25920
25993
  const report = itemReports.find((r) => r.id === artifactId);
25921
- if (report) report.commentSummary = summary;
25994
+ if (report) {
25995
+ report.commentSummary = summary;
25996
+ if (report.progressSource === "status-default") {
25997
+ const pctMatch = summary.match(/(\d{1,3})%/);
25998
+ if (pctMatch) {
25999
+ const pct = parseInt(pctMatch[1], 10);
26000
+ if (pct >= 0 && pct <= 100) {
26001
+ report.progress = pct;
26002
+ report.progressSource = "comment-analysis";
26003
+ }
26004
+ }
26005
+ }
26006
+ }
25922
26007
  }
25923
26008
  } catch (err) {
25924
26009
  errors.push(`Comment analysis failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -25960,7 +26045,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
25960
26045
  totalDays: sprintData.timeline.totalDays,
25961
26046
  percentComplete: sprintData.timeline.percentComplete
25962
26047
  },
25963
- overallProgress: sprintData.workItems.completionPct,
26048
+ overallProgress: rootReports.length > 0 ? computeWeightedProgress(rootReports) : sprintData.workItems.completionPct,
25964
26049
  itemReports: rootReports,
25965
26050
  focusAreas,
25966
26051
  driftItems,
@@ -26057,9 +26142,12 @@ function formatProgressReport(report) {
26057
26142
  parts.push(`## Focus Areas`);
26058
26143
  parts.push("");
26059
26144
  for (const area of report.focusAreas) {
26060
- const bar = progressBar6(area.avgProgress);
26061
- parts.push(`### ${area.name} ${bar} ${area.avgProgress}%`);
26062
- parts.push(`${area.doneCount}/${area.totalCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`);
26145
+ const bar = progressBar6(area.progress);
26146
+ parts.push(`### ${area.name} ${bar} ${area.progress}%`);
26147
+ parts.push(`${area.doneCount}/${area.taskCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`);
26148
+ if (area.riskWarning) {
26149
+ parts.push(` \u26A0 ${area.riskWarning}`);
26150
+ }
26063
26151
  parts.push("");
26064
26152
  for (const item of area.items) {
26065
26153
  formatItemLine(parts, item, 0);
@@ -26123,8 +26211,10 @@ function formatItemLine(parts, item, depth) {
26123
26211
  const statusIcon = DONE_STATUSES16.has(item.marvinStatus) ? "\u2713" : item.marvinStatus === "blocked" ? "\u{1F6AB}" : item.marvinStatus === "in-progress" ? "\u25B6" : "\u25CB";
26124
26212
  const jiraLabel = item.jiraKey ? ` [${item.jiraKey}: ${item.jiraStatus}]` : "";
26125
26213
  const driftFlag = item.statusDrift ? " \u26A0drift" : "";
26126
- const progressLabel = item.marvinProgress > 0 ? ` ${item.marvinProgress}%` : "";
26127
- parts.push(`${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${jiraLabel}${driftFlag}`);
26214
+ const progressLabel = ` ${item.progress}%`;
26215
+ const weightLabel = `w${item.weight}`;
26216
+ const sourceLabel = item.progressSource === "explicit" ? "" : item.progressSource === "comment-analysis" ? " (llm)" : " (est)";
26217
+ parts.push(`${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${sourceLabel} (${weightLabel})${jiraLabel}${driftFlag}`);
26128
26218
  if (item.commentSummary) {
26129
26219
  parts.push(`${indent} \u{1F4AC} ${item.commentSummary}`);
26130
26220
  }
@@ -32572,7 +32662,7 @@ function createProgram() {
32572
32662
  const program = new Command();
32573
32663
  program.name("marvin").description(
32574
32664
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
32575
- ).version("0.5.14");
32665
+ ).version("0.5.15");
32576
32666
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
32577
32667
  await initCommand();
32578
32668
  });