mrvn-cli 0.5.27 → 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 +131 -64
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +130 -63
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +131 -64
- 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,
|
|
@@ -20669,7 +20685,9 @@ ${linkedParts.join("\n")}`);
|
|
|
20669
20685
|
for (const [artifactId, linkedSummaries] of Object.entries(parsed)) {
|
|
20670
20686
|
if (typeof linkedSummaries === "object" && linkedSummaries !== null) {
|
|
20671
20687
|
const signalMap = /* @__PURE__ */ new Map();
|
|
20672
|
-
for (const [key, summary] of Object.entries(
|
|
20688
|
+
for (const [key, summary] of Object.entries(
|
|
20689
|
+
linkedSummaries
|
|
20690
|
+
)) {
|
|
20673
20691
|
if (typeof summary === "string") {
|
|
20674
20692
|
signalMap.set(key, summary);
|
|
20675
20693
|
}
|
|
@@ -20708,7 +20726,9 @@ function formatProgressReport(report) {
|
|
|
20708
20726
|
if (report.timeline.startDate && report.timeline.endDate) {
|
|
20709
20727
|
parts.push(`## Timeline`);
|
|
20710
20728
|
parts.push(`${report.timeline.startDate} \u2192 ${report.timeline.endDate}`);
|
|
20711
|
-
parts.push(
|
|
20729
|
+
parts.push(
|
|
20730
|
+
`Days remaining: ${report.timeline.daysRemaining} / ${report.timeline.totalDays} (${report.timeline.percentComplete}% elapsed)`
|
|
20731
|
+
);
|
|
20712
20732
|
parts.push(`Overall progress: ${report.overallProgress}%`);
|
|
20713
20733
|
parts.push("");
|
|
20714
20734
|
}
|
|
@@ -20718,7 +20738,9 @@ function formatProgressReport(report) {
|
|
|
20718
20738
|
for (const area of report.focusAreas) {
|
|
20719
20739
|
const bar = progressBar(area.progress);
|
|
20720
20740
|
parts.push(`### ${area.name} ${bar} ${area.progress}%`);
|
|
20721
|
-
parts.push(
|
|
20741
|
+
parts.push(
|
|
20742
|
+
`${area.doneCount}/${area.taskCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`
|
|
20743
|
+
);
|
|
20722
20744
|
if (area.riskWarning) {
|
|
20723
20745
|
parts.push(` \u26A0 ${area.riskWarning}`);
|
|
20724
20746
|
}
|
|
@@ -20757,7 +20779,9 @@ function formatProgressReport(report) {
|
|
|
20757
20779
|
if (report.proposedUpdates.length > 0) {
|
|
20758
20780
|
parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
|
|
20759
20781
|
for (const update of report.proposedUpdates) {
|
|
20760
|
-
parts.push(
|
|
20782
|
+
parts.push(
|
|
20783
|
+
` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
20784
|
+
);
|
|
20761
20785
|
parts.push(` Reason: ${update.reason}`);
|
|
20762
20786
|
}
|
|
20763
20787
|
parts.push("");
|
|
@@ -20767,7 +20791,9 @@ function formatProgressReport(report) {
|
|
|
20767
20791
|
if (report.appliedUpdates.length > 0) {
|
|
20768
20792
|
parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
|
|
20769
20793
|
for (const update of report.appliedUpdates) {
|
|
20770
|
-
parts.push(
|
|
20794
|
+
parts.push(
|
|
20795
|
+
` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
20796
|
+
);
|
|
20771
20797
|
}
|
|
20772
20798
|
parts.push("");
|
|
20773
20799
|
}
|
|
@@ -20788,7 +20814,9 @@ function formatItemLine(parts, item, depth) {
|
|
|
20788
20814
|
const progressLabel = ` ${item.progress}%`;
|
|
20789
20815
|
const weightLabel = `w${item.weight}`;
|
|
20790
20816
|
const sourceLabel = item.progressSource === "explicit" ? "" : item.progressSource === "comment-analysis" ? " (llm)" : " (est)";
|
|
20791
|
-
parts.push(
|
|
20817
|
+
parts.push(
|
|
20818
|
+
`${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${sourceLabel} (${weightLabel})${jiraLabel}${driftFlag}`
|
|
20819
|
+
);
|
|
20792
20820
|
if (item.commentSummary) {
|
|
20793
20821
|
parts.push(`${indent} \u{1F4AC} ${item.commentSummary}`);
|
|
20794
20822
|
}
|
|
@@ -20798,7 +20826,9 @@ function formatItemLine(parts, item, depth) {
|
|
|
20798
20826
|
const doneMarker = link.isDone ? " \u2713" : "";
|
|
20799
20827
|
const blockerResolved = link.isDone && BLOCKER_LINK_PATTERNS.some((p) => link.relationship.toLowerCase().includes(p.split(" ")[0])) ? " unblock signal" : "";
|
|
20800
20828
|
const wontDo = WONT_DO_STATUSES.has(link.status.toLowerCase()) ? " \u26A0 needs review" : "";
|
|
20801
|
-
parts.push(
|
|
20829
|
+
parts.push(
|
|
20830
|
+
`${indent} ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}${blockerResolved}${wontDo}`
|
|
20831
|
+
);
|
|
20802
20832
|
const signal = item.linkedIssueSignals.find((s) => s.sourceKey === link.key);
|
|
20803
20833
|
if (signal?.commentSummary) {
|
|
20804
20834
|
parts.push(`${indent} \u{1F4AC} ${signal.commentSummary}`);
|
|
@@ -20817,6 +20847,7 @@ function progressBar(pct) {
|
|
|
20817
20847
|
var MAX_ARTIFACT_NODES = 50;
|
|
20818
20848
|
var MAX_LLM_DEPTH = 3;
|
|
20819
20849
|
var MAX_LLM_COMMENT_CHARS = 8e3;
|
|
20850
|
+
var DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD = 15;
|
|
20820
20851
|
async function assessArtifact(store, client, host, options) {
|
|
20821
20852
|
const visited = /* @__PURE__ */ new Set();
|
|
20822
20853
|
return _assessArtifactRecursive(store, client, host, options, visited, 0);
|
|
@@ -20824,10 +20855,14 @@ async function assessArtifact(store, client, host, options) {
|
|
|
20824
20855
|
async function _assessArtifactRecursive(store, client, host, options, visited, depth) {
|
|
20825
20856
|
const errors = [];
|
|
20826
20857
|
if (visited.has(options.artifactId)) {
|
|
20827
|
-
return emptyArtifactReport(options.artifactId, [
|
|
20858
|
+
return emptyArtifactReport(options.artifactId, [
|
|
20859
|
+
`Cycle detected: ${options.artifactId} already visited`
|
|
20860
|
+
]);
|
|
20828
20861
|
}
|
|
20829
20862
|
if (visited.size >= MAX_ARTIFACT_NODES) {
|
|
20830
|
-
return emptyArtifactReport(options.artifactId, [
|
|
20863
|
+
return emptyArtifactReport(options.artifactId, [
|
|
20864
|
+
`Node cap reached (${MAX_ARTIFACT_NODES}), skipping ${options.artifactId}`
|
|
20865
|
+
]);
|
|
20831
20866
|
}
|
|
20832
20867
|
visited.add(options.artifactId);
|
|
20833
20868
|
const doc = store.get(options.artifactId);
|
|
@@ -20897,27 +20932,29 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
20897
20932
|
if (result.status === "fulfilled") {
|
|
20898
20933
|
const { key, issue: li, comments: lc } = result.value;
|
|
20899
20934
|
linkedJiraIssues.set(key, { issue: li, comments: lc });
|
|
20900
|
-
const newLinks = collectLinkedIssues(li).filter(
|
|
20935
|
+
const newLinks = collectLinkedIssues(li).filter(
|
|
20936
|
+
(l) => l.relationship !== "subtask" && !jiraVisited.has(l.key)
|
|
20937
|
+
);
|
|
20901
20938
|
for (const nl of newLinks) {
|
|
20902
20939
|
jiraVisited.add(nl.key);
|
|
20903
20940
|
queue.push(nl.key);
|
|
20904
20941
|
}
|
|
20905
20942
|
} else {
|
|
20906
20943
|
const batchKey = batch[results.indexOf(result)];
|
|
20907
|
-
errors.push(
|
|
20944
|
+
errors.push(
|
|
20945
|
+
`Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
|
|
20946
|
+
);
|
|
20908
20947
|
}
|
|
20909
20948
|
}
|
|
20910
20949
|
}
|
|
20911
|
-
const { allLinks, allSignals } = collectTransitiveLinks(
|
|
20912
|
-
issue2,
|
|
20913
|
-
jiraIssues,
|
|
20914
|
-
linkedJiraIssues
|
|
20915
|
-
);
|
|
20950
|
+
const { allLinks, allSignals } = collectTransitiveLinks(issue2, jiraIssues, linkedJiraIssues);
|
|
20916
20951
|
linkedIssues = allLinks;
|
|
20917
20952
|
linkedIssueSignals = allSignals;
|
|
20918
20953
|
analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
|
|
20919
20954
|
} catch (err) {
|
|
20920
|
-
errors.push(
|
|
20955
|
+
errors.push(
|
|
20956
|
+
`Failed to fetch ${jiraKey}: ${err instanceof Error ? err.message : String(err)}`
|
|
20957
|
+
);
|
|
20921
20958
|
}
|
|
20922
20959
|
}
|
|
20923
20960
|
const currentProgress = getEffectiveProgress(fm);
|
|
@@ -20958,7 +20995,11 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
20958
20995
|
const primaryHasComments = jiraKey ? (jiraIssues.get(jiraKey)?.comments.length ?? 0) > 0 : false;
|
|
20959
20996
|
let commentAnalysisProgress = null;
|
|
20960
20997
|
if (depth < MAX_LLM_DEPTH && jiraKey && primaryHasComments) {
|
|
20961
|
-
const estimatedChars = estimateCommentTextSize(
|
|
20998
|
+
const estimatedChars = estimateCommentTextSize(
|
|
20999
|
+
jiraIssues,
|
|
21000
|
+
linkedJiraIssues,
|
|
21001
|
+
linkedIssueSignals
|
|
21002
|
+
);
|
|
20962
21003
|
if (estimatedChars <= MAX_LLM_COMMENT_CHARS) {
|
|
20963
21004
|
try {
|
|
20964
21005
|
const analysis = await analyzeSingleArtifactComments(
|
|
@@ -20973,14 +21014,16 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
20973
21014
|
commentSummary = analysis.summary;
|
|
20974
21015
|
commentAnalysisProgress = analysis.progressEstimate;
|
|
20975
21016
|
if (commentAnalysisProgress !== null) {
|
|
20976
|
-
const
|
|
20977
|
-
|
|
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" : "";
|
|
20978
21021
|
proposedUpdates.push({
|
|
20979
21022
|
artifactId: fm.id,
|
|
20980
21023
|
field: "progress",
|
|
20981
21024
|
currentValue: currentProgress,
|
|
20982
21025
|
proposedValue: commentAnalysisProgress,
|
|
20983
|
-
reason: `Comment
|
|
21026
|
+
reason: `Comment-derived estimate (${commentAnalysisProgress}%) diverges from current (${currentProgress}%) by ${divergence}pp${overrideWarning}`
|
|
20984
21027
|
});
|
|
20985
21028
|
}
|
|
20986
21029
|
}
|
|
@@ -20993,7 +21036,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
20993
21036
|
const children = [];
|
|
20994
21037
|
for (const childId of childIds) {
|
|
20995
21038
|
if (visited.size >= MAX_ARTIFACT_NODES) {
|
|
20996
|
-
errors.push(
|
|
21039
|
+
errors.push(
|
|
21040
|
+
`Node cap reached (${MAX_ARTIFACT_NODES}), ${childIds.length - children.length} children skipped`
|
|
21041
|
+
);
|
|
20997
21042
|
break;
|
|
20998
21043
|
}
|
|
20999
21044
|
const childReport = await _assessArtifactRecursive(
|
|
@@ -21037,7 +21082,10 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
21037
21082
|
blockerProgressValue = blockerResult.blockerProgress;
|
|
21038
21083
|
totalBlockersCount = blockerResult.totalBlockers;
|
|
21039
21084
|
resolvedBlockersCount = blockerResult.resolvedBlockers;
|
|
21040
|
-
const lastProgressUpdate = findLast(
|
|
21085
|
+
const lastProgressUpdate = findLast(
|
|
21086
|
+
proposedUpdates,
|
|
21087
|
+
(u) => u.artifactId === fm.id && u.field === "progress"
|
|
21088
|
+
);
|
|
21041
21089
|
const implementationProgress = lastProgressUpdate ? lastProgressUpdate.proposedValue : currentProgress;
|
|
21042
21090
|
const combinedProgress = Math.round(
|
|
21043
21091
|
blockerResult.blockerProgress + implementationProgress * (1 - prerequisiteWeight)
|
|
@@ -21132,7 +21180,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
21132
21180
|
}
|
|
21133
21181
|
store.update(fm.id, payload);
|
|
21134
21182
|
} catch (err) {
|
|
21135
|
-
errors.push(
|
|
21183
|
+
errors.push(
|
|
21184
|
+
`Failed to persist assessment history: ${err instanceof Error ? err.message : String(err)}`
|
|
21185
|
+
);
|
|
21136
21186
|
}
|
|
21137
21187
|
}
|
|
21138
21188
|
return {
|
|
@@ -21192,9 +21242,7 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
|
|
|
21192
21242
|
signals.push(`\u{1F6AB} Blocker \u2014 "${s.snippet}"`);
|
|
21193
21243
|
}
|
|
21194
21244
|
}
|
|
21195
|
-
const blockingLinks = linkedIssues.filter(
|
|
21196
|
-
(l) => l.relationship.toLowerCase().includes("block")
|
|
21197
|
-
);
|
|
21245
|
+
const blockingLinks = linkedIssues.filter((l) => l.relationship.toLowerCase().includes("block"));
|
|
21198
21246
|
const activeBlockers = blockingLinks.filter((l) => !l.isDone);
|
|
21199
21247
|
const resolvedBlockers = blockingLinks.filter((l) => l.isDone);
|
|
21200
21248
|
if (activeBlockers.length > 0) {
|
|
@@ -21203,7 +21251,9 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
|
|
|
21203
21251
|
}
|
|
21204
21252
|
}
|
|
21205
21253
|
if (resolvedBlockers.length > 0 && activeBlockers.length === 0) {
|
|
21206
|
-
signals.push(
|
|
21254
|
+
signals.push(
|
|
21255
|
+
`\u2705 Unblocked \u2014 all blocking issues resolved: ${resolvedBlockers.map((l) => l.key).join(", ")}`
|
|
21256
|
+
);
|
|
21207
21257
|
}
|
|
21208
21258
|
const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
|
|
21209
21259
|
for (const l of wontDoLinks) {
|
|
@@ -21265,9 +21315,11 @@ async function analyzeSingleArtifactComments(artifactId, title, jiraKey, jiraSta
|
|
|
21265
21315
|
const text = extractCommentText(c.body);
|
|
21266
21316
|
return `[${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
|
|
21267
21317
|
}).join("\n");
|
|
21268
|
-
promptParts.push(
|
|
21318
|
+
promptParts.push(
|
|
21319
|
+
`## ${artifactId} \u2014 ${title} (${jiraKey}, status: ${jiraStatus})
|
|
21269
21320
|
Comments:
|
|
21270
|
-
${commentTexts}`
|
|
21321
|
+
${commentTexts}`
|
|
21322
|
+
);
|
|
21271
21323
|
}
|
|
21272
21324
|
for (const signal of linkedIssueSignals) {
|
|
21273
21325
|
const linkedData = linkedJiraIssues.get(signal.sourceKey);
|
|
@@ -21356,7 +21408,7 @@ function buildAssessmentSummary(commentSummary, commentAnalysisProgress, signals
|
|
|
21356
21408
|
if (lastProgress) return lastProgress.proposedValue;
|
|
21357
21409
|
return c.marvinProgress;
|
|
21358
21410
|
});
|
|
21359
|
-
const childDoneCount = children.filter((c
|
|
21411
|
+
const childDoneCount = children.filter((c) => {
|
|
21360
21412
|
const updates = c.appliedUpdates.length > 0 ? c.appliedUpdates : c.proposedUpdates;
|
|
21361
21413
|
const lastStatus = findLast(updates, (u) => u.field === "status");
|
|
21362
21414
|
const effectiveStatus = lastStatus ? String(lastStatus.proposedValue) : c.marvinStatus;
|
|
@@ -21392,7 +21444,8 @@ function formatArtifactReport(report) {
|
|
|
21392
21444
|
parts.push(`## Jira State (${report.jiraKey})`);
|
|
21393
21445
|
const jiraParts = [`Status: ${report.jiraStatus ?? "unknown"}`];
|
|
21394
21446
|
if (report.jiraAssignee) jiraParts.push(`Assignee: ${report.jiraAssignee}`);
|
|
21395
|
-
if (report.jiraSubtaskProgress !== null)
|
|
21447
|
+
if (report.jiraSubtaskProgress !== null)
|
|
21448
|
+
jiraParts.push(`Subtask progress: ${report.jiraSubtaskProgress}%`);
|
|
21396
21449
|
parts.push(jiraParts.join(" | "));
|
|
21397
21450
|
if (report.statusDrift) {
|
|
21398
21451
|
parts.push(`\u26A0 Drift: ${report.marvinStatus} \u2192 ${report.proposedMarvinStatus}`);
|
|
@@ -21413,7 +21466,9 @@ function formatArtifactReport(report) {
|
|
|
21413
21466
|
if (report.totalBlockers > 0) {
|
|
21414
21467
|
parts.push(`## Blocker Resolution`);
|
|
21415
21468
|
const bpLabel = report.blockerProgress !== null ? `${report.blockerProgress}%` : "n/a (skipped)";
|
|
21416
|
-
parts.push(
|
|
21469
|
+
parts.push(
|
|
21470
|
+
` ${report.resolvedBlockers}/${report.totalBlockers} blockers resolved \u2192 ${bpLabel} prerequisite progress`
|
|
21471
|
+
);
|
|
21417
21472
|
parts.push("");
|
|
21418
21473
|
}
|
|
21419
21474
|
if (report.children.length > 0) {
|
|
@@ -21422,7 +21477,9 @@ function formatArtifactReport(report) {
|
|
|
21422
21477
|
report.children.reduce((s, c) => s + c.marvinProgress, 0) / report.children.length
|
|
21423
21478
|
);
|
|
21424
21479
|
const bar = progressBar(childProgress);
|
|
21425
|
-
parts.push(
|
|
21480
|
+
parts.push(
|
|
21481
|
+
`## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`
|
|
21482
|
+
);
|
|
21426
21483
|
for (const child of report.children) {
|
|
21427
21484
|
formatArtifactChild(parts, child, 1);
|
|
21428
21485
|
}
|
|
@@ -21432,7 +21489,9 @@ function formatArtifactReport(report) {
|
|
|
21432
21489
|
parts.push(`## Linked Issues (${report.linkedIssues.length})`);
|
|
21433
21490
|
for (const link of report.linkedIssues) {
|
|
21434
21491
|
const doneMarker = link.isDone ? " \u2713" : "";
|
|
21435
|
-
parts.push(
|
|
21492
|
+
parts.push(
|
|
21493
|
+
` ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}`
|
|
21494
|
+
);
|
|
21436
21495
|
const signal = report.linkedIssueSignals.find((s) => s.sourceKey === link.key);
|
|
21437
21496
|
if (signal?.commentSummary) {
|
|
21438
21497
|
parts.push(` \u{1F4AC} ${signal.commentSummary}`);
|
|
@@ -21450,7 +21509,9 @@ function formatArtifactReport(report) {
|
|
|
21450
21509
|
if (report.proposedUpdates.length > 0) {
|
|
21451
21510
|
parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
|
|
21452
21511
|
for (const update of report.proposedUpdates) {
|
|
21453
|
-
parts.push(
|
|
21512
|
+
parts.push(
|
|
21513
|
+
` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
21514
|
+
);
|
|
21454
21515
|
parts.push(` Reason: ${update.reason}`);
|
|
21455
21516
|
}
|
|
21456
21517
|
parts.push("");
|
|
@@ -21460,7 +21521,9 @@ function formatArtifactReport(report) {
|
|
|
21460
21521
|
if (report.appliedUpdates.length > 0) {
|
|
21461
21522
|
parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
|
|
21462
21523
|
for (const update of report.appliedUpdates) {
|
|
21463
|
-
parts.push(
|
|
21524
|
+
parts.push(
|
|
21525
|
+
` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
21526
|
+
);
|
|
21464
21527
|
}
|
|
21465
21528
|
parts.push("");
|
|
21466
21529
|
}
|
|
@@ -21483,7 +21546,9 @@ function formatArtifactChild(parts, child, depth) {
|
|
|
21483
21546
|
if (s.startsWith("\u2705 No active")) continue;
|
|
21484
21547
|
signalHints.push(s);
|
|
21485
21548
|
}
|
|
21486
|
-
parts.push(
|
|
21549
|
+
parts.push(
|
|
21550
|
+
`${indent}${icon} ${child.artifactId} \u2014 ${child.title} [${child.marvinStatus}] ${child.marvinProgress}%${jiraLabel}${driftLabel}`
|
|
21551
|
+
);
|
|
21487
21552
|
if (child.commentSummary) {
|
|
21488
21553
|
parts.push(`${indent} \u{1F4AC} ${child.commentSummary}`);
|
|
21489
21554
|
}
|
|
@@ -22318,7 +22383,8 @@ function createJiraTools(store, projectConfig) {
|
|
|
22318
22383
|
{
|
|
22319
22384
|
artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'T-063', 'A-151', 'E-003')"),
|
|
22320
22385
|
applyUpdates: external_exports.boolean().optional().describe("Apply proposed status/progress updates to the artifact (default false)"),
|
|
22321
|
-
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.")
|
|
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).")
|
|
22322
22388
|
},
|
|
22323
22389
|
async (args) => {
|
|
22324
22390
|
const jira = createJiraClient(jiraUserConfig);
|
|
@@ -22331,6 +22397,7 @@ function createJiraTools(store, projectConfig) {
|
|
|
22331
22397
|
artifactId: args.artifactId,
|
|
22332
22398
|
applyUpdates: args.applyUpdates ?? false,
|
|
22333
22399
|
prerequisiteWeight: args.prerequisiteWeight,
|
|
22400
|
+
progressDivergenceThreshold: args.progressDivergenceThreshold,
|
|
22334
22401
|
statusMap
|
|
22335
22402
|
}
|
|
22336
22403
|
);
|