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 +110 -20
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +109 -19
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +110 -20
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin-serve.js
CHANGED
|
@@ -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
|
-
|
|
19929
|
-
if (!focusAreaMap.has(
|
|
19930
|
-
focusAreaMap.get(
|
|
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
|
|
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
|
-
|
|
19941
|
-
|
|
20015
|
+
progress,
|
|
20016
|
+
taskCount: allFlatItems.length,
|
|
19942
20017
|
doneCount,
|
|
19943
20018
|
blockedCount,
|
|
19944
|
-
|
|
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)
|
|
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.
|
|
20107
|
-
parts.push(`### ${area.name} ${bar} ${area.
|
|
20108
|
-
parts.push(`${area.doneCount}/${area.
|
|
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 =
|
|
20173
|
-
|
|
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
|
}
|