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.
- package/dist/index.js +203 -67
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +202 -66
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +203 -67
- package/dist/marvin.js.map +1 -1
- package/package.json +25 -2
package/dist/marvin-serve.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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: {
|
|
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: [
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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, [
|
|
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, [
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
20968
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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)
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
}
|