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/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
|
-
|
|
25883
|
-
if (!focusAreaMap.has(
|
|
25884
|
-
focusAreaMap.get(
|
|
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
|
|
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
|
-
|
|
25895
|
-
|
|
25969
|
+
progress,
|
|
25970
|
+
taskCount: allFlatItems.length,
|
|
25896
25971
|
doneCount,
|
|
25897
25972
|
blockedCount,
|
|
25898
|
-
|
|
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)
|
|
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.
|
|
26061
|
-
parts.push(`### ${area.name} ${bar} ${area.
|
|
26062
|
-
parts.push(`${area.doneCount}/${area.
|
|
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 =
|
|
26127
|
-
|
|
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.
|
|
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
|
});
|