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/index.js
CHANGED
|
@@ -26507,10 +26507,16 @@ function resolveWeight(complexity) {
|
|
|
26507
26507
|
function resolveProgress(frontmatter, commentAnalysisProgress) {
|
|
26508
26508
|
const hasExplicitProgress = "progress" in frontmatter && typeof frontmatter.progress === "number";
|
|
26509
26509
|
if (hasExplicitProgress) {
|
|
26510
|
-
return {
|
|
26510
|
+
return {
|
|
26511
|
+
progress: Math.max(0, Math.min(100, Math.round(frontmatter.progress))),
|
|
26512
|
+
progressSource: "explicit"
|
|
26513
|
+
};
|
|
26511
26514
|
}
|
|
26512
26515
|
if (commentAnalysisProgress !== null) {
|
|
26513
|
-
return {
|
|
26516
|
+
return {
|
|
26517
|
+
progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))),
|
|
26518
|
+
progressSource: "comment-analysis"
|
|
26519
|
+
};
|
|
26514
26520
|
}
|
|
26515
26521
|
const status = frontmatter.status;
|
|
26516
26522
|
const defaultProgress = STATUS_PROGRESS_DEFAULTS[status] ?? 0;
|
|
@@ -26535,7 +26541,13 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26535
26541
|
sprintId: options.sprintId ?? "unknown",
|
|
26536
26542
|
sprintTitle: "Sprint not found",
|
|
26537
26543
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26538
|
-
timeline: {
|
|
26544
|
+
timeline: {
|
|
26545
|
+
startDate: null,
|
|
26546
|
+
endDate: null,
|
|
26547
|
+
daysRemaining: 0,
|
|
26548
|
+
totalDays: 0,
|
|
26549
|
+
percentComplete: 0
|
|
26550
|
+
},
|
|
26539
26551
|
overallProgress: 0,
|
|
26540
26552
|
itemReports: [],
|
|
26541
26553
|
focusAreas: [],
|
|
@@ -26543,7 +26555,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26543
26555
|
blockers: [],
|
|
26544
26556
|
proposedUpdates: [],
|
|
26545
26557
|
appliedUpdates: [],
|
|
26546
|
-
errors: [
|
|
26558
|
+
errors: [
|
|
26559
|
+
`Sprint ${options.sprintId ?? "(active)"} not found. Create a sprint artifact first.`
|
|
26560
|
+
]
|
|
26547
26561
|
};
|
|
26548
26562
|
}
|
|
26549
26563
|
const sprintTag = `sprint:${sprintData.sprint.id}`;
|
|
@@ -26587,7 +26601,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26587
26601
|
});
|
|
26588
26602
|
} else {
|
|
26589
26603
|
const batchKey = batch[results.indexOf(result)];
|
|
26590
|
-
errors.push(
|
|
26604
|
+
errors.push(
|
|
26605
|
+
`Failed to fetch ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
|
|
26606
|
+
);
|
|
26591
26607
|
}
|
|
26592
26608
|
}
|
|
26593
26609
|
}
|
|
@@ -26629,12 +26645,16 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26629
26645
|
}
|
|
26630
26646
|
} else {
|
|
26631
26647
|
const batchKey = batch[results.indexOf(result)];
|
|
26632
|
-
errors.push(
|
|
26648
|
+
errors.push(
|
|
26649
|
+
`Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
|
|
26650
|
+
);
|
|
26633
26651
|
}
|
|
26634
26652
|
}
|
|
26635
26653
|
}
|
|
26636
26654
|
if (queue.length > 0) {
|
|
26637
|
-
errors.push(
|
|
26655
|
+
errors.push(
|
|
26656
|
+
`Link traversal capped at ${MAX_LINKED_ISSUES} linked issues (${queue.length} remaining undiscovered)`
|
|
26657
|
+
);
|
|
26638
26658
|
}
|
|
26639
26659
|
}
|
|
26640
26660
|
const proposedUpdates = [];
|
|
@@ -26712,12 +26732,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26712
26732
|
);
|
|
26713
26733
|
itemLinkedIssues = allLinks;
|
|
26714
26734
|
itemLinkedIssueSignals.push(...allSignals);
|
|
26715
|
-
analyzeLinkedIssueSignals(
|
|
26716
|
-
allLinks,
|
|
26717
|
-
fm,
|
|
26718
|
-
jiraKey,
|
|
26719
|
-
proposedUpdates
|
|
26720
|
-
);
|
|
26735
|
+
analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
|
|
26721
26736
|
}
|
|
26722
26737
|
const report = {
|
|
26723
26738
|
id: fm.id,
|
|
@@ -26836,10 +26851,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26836
26851
|
}
|
|
26837
26852
|
if (options.traverseLinks) {
|
|
26838
26853
|
try {
|
|
26839
|
-
const linkedSummaries = await analyzeLinkedIssueComments(
|
|
26840
|
-
itemReports,
|
|
26841
|
-
linkedJiraIssues
|
|
26842
|
-
);
|
|
26854
|
+
const linkedSummaries = await analyzeLinkedIssueComments(itemReports, linkedJiraIssues);
|
|
26843
26855
|
for (const [artifactId, signalSummaries] of linkedSummaries) {
|
|
26844
26856
|
const report = itemReports.find((r) => r.id === artifactId);
|
|
26845
26857
|
if (!report) continue;
|
|
@@ -26851,7 +26863,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26851
26863
|
}
|
|
26852
26864
|
}
|
|
26853
26865
|
} catch (err) {
|
|
26854
|
-
errors.push(
|
|
26866
|
+
errors.push(
|
|
26867
|
+
`Linked issue comment analysis failed: ${err instanceof Error ? err.message : String(err)}`
|
|
26868
|
+
);
|
|
26855
26869
|
}
|
|
26856
26870
|
}
|
|
26857
26871
|
}
|
|
@@ -26923,9 +26937,11 @@ async function analyzeCommentsForProgress(items, jiraIssues, itemJiraKeys) {
|
|
|
26923
26937
|
const text = extractCommentText(c.body);
|
|
26924
26938
|
return ` [${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
|
|
26925
26939
|
}).join("\n");
|
|
26926
|
-
promptParts.push(
|
|
26940
|
+
promptParts.push(
|
|
26941
|
+
`## ${item.id} \u2014 ${item.title} (${jiraKey}, Jira status: ${item.jiraStatus})
|
|
26927
26942
|
Comments:
|
|
26928
|
-
${commentTexts}`
|
|
26943
|
+
${commentTexts}`
|
|
26944
|
+
);
|
|
26929
26945
|
}
|
|
26930
26946
|
if (promptParts.length === 0) return summaries;
|
|
26931
26947
|
const prompt = promptParts.join("\n\n");
|
|
@@ -26998,7 +27014,9 @@ function collectTransitiveLinks(primaryIssue, primaryIssues, linkedJiraIssues) {
|
|
|
26998
27014
|
commentSummary: null
|
|
26999
27015
|
});
|
|
27000
27016
|
}
|
|
27001
|
-
const nextLinks = collectLinkedIssues(linkedData.issue).filter(
|
|
27017
|
+
const nextLinks = collectLinkedIssues(linkedData.issue).filter(
|
|
27018
|
+
(l) => l.relationship !== "subtask" && !visited.has(l.key)
|
|
27019
|
+
);
|
|
27002
27020
|
for (const next of nextLinks) {
|
|
27003
27021
|
visited.add(next.key);
|
|
27004
27022
|
queue.push(next);
|
|
@@ -27022,9 +27040,7 @@ function analyzeLinkedIssueSignals(linkedIssues, frontmatter, jiraKey, proposedU
|
|
|
27022
27040
|
reason: `All blocking issues resolved: ${blockerLinks.map((l) => l.key).join(", ")}`
|
|
27023
27041
|
});
|
|
27024
27042
|
}
|
|
27025
|
-
const wontDoLinks = linkedIssues.filter(
|
|
27026
|
-
(l) => WONT_DO_STATUSES.has(l.status.toLowerCase())
|
|
27027
|
-
);
|
|
27043
|
+
const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
|
|
27028
27044
|
if (wontDoLinks.length > 0) {
|
|
27029
27045
|
proposedUpdates.push({
|
|
27030
27046
|
artifactId: frontmatter.id,
|
|
@@ -27098,7 +27114,9 @@ ${linkedParts.join("\n")}`);
|
|
|
27098
27114
|
for (const [artifactId, linkedSummaries] of Object.entries(parsed)) {
|
|
27099
27115
|
if (typeof linkedSummaries === "object" && linkedSummaries !== null) {
|
|
27100
27116
|
const signalMap = /* @__PURE__ */ new Map();
|
|
27101
|
-
for (const [key, summary] of Object.entries(
|
|
27117
|
+
for (const [key, summary] of Object.entries(
|
|
27118
|
+
linkedSummaries
|
|
27119
|
+
)) {
|
|
27102
27120
|
if (typeof summary === "string") {
|
|
27103
27121
|
signalMap.set(key, summary);
|
|
27104
27122
|
}
|
|
@@ -27137,7 +27155,9 @@ function formatProgressReport(report) {
|
|
|
27137
27155
|
if (report.timeline.startDate && report.timeline.endDate) {
|
|
27138
27156
|
parts.push(`## Timeline`);
|
|
27139
27157
|
parts.push(`${report.timeline.startDate} \u2192 ${report.timeline.endDate}`);
|
|
27140
|
-
parts.push(
|
|
27158
|
+
parts.push(
|
|
27159
|
+
`Days remaining: ${report.timeline.daysRemaining} / ${report.timeline.totalDays} (${report.timeline.percentComplete}% elapsed)`
|
|
27160
|
+
);
|
|
27141
27161
|
parts.push(`Overall progress: ${report.overallProgress}%`);
|
|
27142
27162
|
parts.push("");
|
|
27143
27163
|
}
|
|
@@ -27147,7 +27167,9 @@ function formatProgressReport(report) {
|
|
|
27147
27167
|
for (const area of report.focusAreas) {
|
|
27148
27168
|
const bar = progressBar6(area.progress);
|
|
27149
27169
|
parts.push(`### ${area.name} ${bar} ${area.progress}%`);
|
|
27150
|
-
parts.push(
|
|
27170
|
+
parts.push(
|
|
27171
|
+
`${area.doneCount}/${area.taskCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`
|
|
27172
|
+
);
|
|
27151
27173
|
if (area.riskWarning) {
|
|
27152
27174
|
parts.push(` \u26A0 ${area.riskWarning}`);
|
|
27153
27175
|
}
|
|
@@ -27186,7 +27208,9 @@ function formatProgressReport(report) {
|
|
|
27186
27208
|
if (report.proposedUpdates.length > 0) {
|
|
27187
27209
|
parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
|
|
27188
27210
|
for (const update of report.proposedUpdates) {
|
|
27189
|
-
parts.push(
|
|
27211
|
+
parts.push(
|
|
27212
|
+
` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
27213
|
+
);
|
|
27190
27214
|
parts.push(` Reason: ${update.reason}`);
|
|
27191
27215
|
}
|
|
27192
27216
|
parts.push("");
|
|
@@ -27196,7 +27220,9 @@ function formatProgressReport(report) {
|
|
|
27196
27220
|
if (report.appliedUpdates.length > 0) {
|
|
27197
27221
|
parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
|
|
27198
27222
|
for (const update of report.appliedUpdates) {
|
|
27199
|
-
parts.push(
|
|
27223
|
+
parts.push(
|
|
27224
|
+
` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
27225
|
+
);
|
|
27200
27226
|
}
|
|
27201
27227
|
parts.push("");
|
|
27202
27228
|
}
|
|
@@ -27217,7 +27243,9 @@ function formatItemLine(parts, item, depth) {
|
|
|
27217
27243
|
const progressLabel = ` ${item.progress}%`;
|
|
27218
27244
|
const weightLabel = `w${item.weight}`;
|
|
27219
27245
|
const sourceLabel = item.progressSource === "explicit" ? "" : item.progressSource === "comment-analysis" ? " (llm)" : " (est)";
|
|
27220
|
-
parts.push(
|
|
27246
|
+
parts.push(
|
|
27247
|
+
`${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${sourceLabel} (${weightLabel})${jiraLabel}${driftFlag}`
|
|
27248
|
+
);
|
|
27221
27249
|
if (item.commentSummary) {
|
|
27222
27250
|
parts.push(`${indent} \u{1F4AC} ${item.commentSummary}`);
|
|
27223
27251
|
}
|
|
@@ -27227,7 +27255,9 @@ function formatItemLine(parts, item, depth) {
|
|
|
27227
27255
|
const doneMarker = link.isDone ? " \u2713" : "";
|
|
27228
27256
|
const blockerResolved = link.isDone && BLOCKER_LINK_PATTERNS.some((p) => link.relationship.toLowerCase().includes(p.split(" ")[0])) ? " unblock signal" : "";
|
|
27229
27257
|
const wontDo = WONT_DO_STATUSES.has(link.status.toLowerCase()) ? " \u26A0 needs review" : "";
|
|
27230
|
-
parts.push(
|
|
27258
|
+
parts.push(
|
|
27259
|
+
`${indent} ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}${blockerResolved}${wontDo}`
|
|
27260
|
+
);
|
|
27231
27261
|
const signal = item.linkedIssueSignals.find((s) => s.sourceKey === link.key);
|
|
27232
27262
|
if (signal?.commentSummary) {
|
|
27233
27263
|
parts.push(`${indent} \u{1F4AC} ${signal.commentSummary}`);
|
|
@@ -27246,6 +27276,7 @@ function progressBar6(pct) {
|
|
|
27246
27276
|
var MAX_ARTIFACT_NODES = 50;
|
|
27247
27277
|
var MAX_LLM_DEPTH = 3;
|
|
27248
27278
|
var MAX_LLM_COMMENT_CHARS = 8e3;
|
|
27279
|
+
var DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD = 15;
|
|
27249
27280
|
async function assessArtifact(store, client, host, options) {
|
|
27250
27281
|
const visited = /* @__PURE__ */ new Set();
|
|
27251
27282
|
return _assessArtifactRecursive(store, client, host, options, visited, 0);
|
|
@@ -27253,10 +27284,14 @@ async function assessArtifact(store, client, host, options) {
|
|
|
27253
27284
|
async function _assessArtifactRecursive(store, client, host, options, visited, depth) {
|
|
27254
27285
|
const errors = [];
|
|
27255
27286
|
if (visited.has(options.artifactId)) {
|
|
27256
|
-
return emptyArtifactReport(options.artifactId, [
|
|
27287
|
+
return emptyArtifactReport(options.artifactId, [
|
|
27288
|
+
`Cycle detected: ${options.artifactId} already visited`
|
|
27289
|
+
]);
|
|
27257
27290
|
}
|
|
27258
27291
|
if (visited.size >= MAX_ARTIFACT_NODES) {
|
|
27259
|
-
return emptyArtifactReport(options.artifactId, [
|
|
27292
|
+
return emptyArtifactReport(options.artifactId, [
|
|
27293
|
+
`Node cap reached (${MAX_ARTIFACT_NODES}), skipping ${options.artifactId}`
|
|
27294
|
+
]);
|
|
27260
27295
|
}
|
|
27261
27296
|
visited.add(options.artifactId);
|
|
27262
27297
|
const doc = store.get(options.artifactId);
|
|
@@ -27326,27 +27361,29 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27326
27361
|
if (result.status === "fulfilled") {
|
|
27327
27362
|
const { key, issue: li, comments: lc } = result.value;
|
|
27328
27363
|
linkedJiraIssues.set(key, { issue: li, comments: lc });
|
|
27329
|
-
const newLinks = collectLinkedIssues(li).filter(
|
|
27364
|
+
const newLinks = collectLinkedIssues(li).filter(
|
|
27365
|
+
(l) => l.relationship !== "subtask" && !jiraVisited.has(l.key)
|
|
27366
|
+
);
|
|
27330
27367
|
for (const nl of newLinks) {
|
|
27331
27368
|
jiraVisited.add(nl.key);
|
|
27332
27369
|
queue.push(nl.key);
|
|
27333
27370
|
}
|
|
27334
27371
|
} else {
|
|
27335
27372
|
const batchKey = batch[results.indexOf(result)];
|
|
27336
|
-
errors.push(
|
|
27373
|
+
errors.push(
|
|
27374
|
+
`Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
|
|
27375
|
+
);
|
|
27337
27376
|
}
|
|
27338
27377
|
}
|
|
27339
27378
|
}
|
|
27340
|
-
const { allLinks, allSignals } = collectTransitiveLinks(
|
|
27341
|
-
issue2,
|
|
27342
|
-
jiraIssues,
|
|
27343
|
-
linkedJiraIssues
|
|
27344
|
-
);
|
|
27379
|
+
const { allLinks, allSignals } = collectTransitiveLinks(issue2, jiraIssues, linkedJiraIssues);
|
|
27345
27380
|
linkedIssues = allLinks;
|
|
27346
27381
|
linkedIssueSignals = allSignals;
|
|
27347
27382
|
analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
|
|
27348
27383
|
} catch (err) {
|
|
27349
|
-
errors.push(
|
|
27384
|
+
errors.push(
|
|
27385
|
+
`Failed to fetch ${jiraKey}: ${err instanceof Error ? err.message : String(err)}`
|
|
27386
|
+
);
|
|
27350
27387
|
}
|
|
27351
27388
|
}
|
|
27352
27389
|
const currentProgress = getEffectiveProgress(fm);
|
|
@@ -27387,7 +27424,11 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27387
27424
|
const primaryHasComments = jiraKey ? (jiraIssues.get(jiraKey)?.comments.length ?? 0) > 0 : false;
|
|
27388
27425
|
let commentAnalysisProgress = null;
|
|
27389
27426
|
if (depth < MAX_LLM_DEPTH && jiraKey && primaryHasComments) {
|
|
27390
|
-
const estimatedChars = estimateCommentTextSize(
|
|
27427
|
+
const estimatedChars = estimateCommentTextSize(
|
|
27428
|
+
jiraIssues,
|
|
27429
|
+
linkedJiraIssues,
|
|
27430
|
+
linkedIssueSignals
|
|
27431
|
+
);
|
|
27391
27432
|
if (estimatedChars <= MAX_LLM_COMMENT_CHARS) {
|
|
27392
27433
|
try {
|
|
27393
27434
|
const analysis = await analyzeSingleArtifactComments(
|
|
@@ -27402,14 +27443,16 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27402
27443
|
commentSummary = analysis.summary;
|
|
27403
27444
|
commentAnalysisProgress = analysis.progressEstimate;
|
|
27404
27445
|
if (commentAnalysisProgress !== null) {
|
|
27405
|
-
const
|
|
27406
|
-
|
|
27446
|
+
const divergence = Math.abs(commentAnalysisProgress - currentProgress);
|
|
27447
|
+
const threshold = options.progressDivergenceThreshold ?? DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD;
|
|
27448
|
+
if (divergence >= threshold && commentAnalysisProgress !== currentProgress) {
|
|
27449
|
+
const overrideWarning = fm.progressOverride ? " \u26A0 progressOverride is set \u2014 review before applying" : "";
|
|
27407
27450
|
proposedUpdates.push({
|
|
27408
27451
|
artifactId: fm.id,
|
|
27409
27452
|
field: "progress",
|
|
27410
27453
|
currentValue: currentProgress,
|
|
27411
27454
|
proposedValue: commentAnalysisProgress,
|
|
27412
|
-
reason: `Comment
|
|
27455
|
+
reason: `Comment-derived estimate (${commentAnalysisProgress}%) diverges from current (${currentProgress}%) by ${divergence}pp${overrideWarning}`
|
|
27413
27456
|
});
|
|
27414
27457
|
}
|
|
27415
27458
|
}
|
|
@@ -27422,7 +27465,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27422
27465
|
const children = [];
|
|
27423
27466
|
for (const childId of childIds) {
|
|
27424
27467
|
if (visited.size >= MAX_ARTIFACT_NODES) {
|
|
27425
|
-
errors.push(
|
|
27468
|
+
errors.push(
|
|
27469
|
+
`Node cap reached (${MAX_ARTIFACT_NODES}), ${childIds.length - children.length} children skipped`
|
|
27470
|
+
);
|
|
27426
27471
|
break;
|
|
27427
27472
|
}
|
|
27428
27473
|
const childReport = await _assessArtifactRecursive(
|
|
@@ -27466,7 +27511,10 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27466
27511
|
blockerProgressValue = blockerResult.blockerProgress;
|
|
27467
27512
|
totalBlockersCount = blockerResult.totalBlockers;
|
|
27468
27513
|
resolvedBlockersCount = blockerResult.resolvedBlockers;
|
|
27469
|
-
const lastProgressUpdate = findLast(
|
|
27514
|
+
const lastProgressUpdate = findLast(
|
|
27515
|
+
proposedUpdates,
|
|
27516
|
+
(u) => u.artifactId === fm.id && u.field === "progress"
|
|
27517
|
+
);
|
|
27470
27518
|
const implementationProgress = lastProgressUpdate ? lastProgressUpdate.proposedValue : currentProgress;
|
|
27471
27519
|
const combinedProgress = Math.round(
|
|
27472
27520
|
blockerResult.blockerProgress + implementationProgress * (1 - prerequisiteWeight)
|
|
@@ -27561,7 +27609,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27561
27609
|
}
|
|
27562
27610
|
store.update(fm.id, payload);
|
|
27563
27611
|
} catch (err) {
|
|
27564
|
-
errors.push(
|
|
27612
|
+
errors.push(
|
|
27613
|
+
`Failed to persist assessment history: ${err instanceof Error ? err.message : String(err)}`
|
|
27614
|
+
);
|
|
27565
27615
|
}
|
|
27566
27616
|
}
|
|
27567
27617
|
return {
|
|
@@ -27621,9 +27671,7 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
|
|
|
27621
27671
|
signals.push(`\u{1F6AB} Blocker \u2014 "${s.snippet}"`);
|
|
27622
27672
|
}
|
|
27623
27673
|
}
|
|
27624
|
-
const blockingLinks = linkedIssues.filter(
|
|
27625
|
-
(l) => l.relationship.toLowerCase().includes("block")
|
|
27626
|
-
);
|
|
27674
|
+
const blockingLinks = linkedIssues.filter((l) => l.relationship.toLowerCase().includes("block"));
|
|
27627
27675
|
const activeBlockers = blockingLinks.filter((l) => !l.isDone);
|
|
27628
27676
|
const resolvedBlockers = blockingLinks.filter((l) => l.isDone);
|
|
27629
27677
|
if (activeBlockers.length > 0) {
|
|
@@ -27632,7 +27680,9 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
|
|
|
27632
27680
|
}
|
|
27633
27681
|
}
|
|
27634
27682
|
if (resolvedBlockers.length > 0 && activeBlockers.length === 0) {
|
|
27635
|
-
signals.push(
|
|
27683
|
+
signals.push(
|
|
27684
|
+
`\u2705 Unblocked \u2014 all blocking issues resolved: ${resolvedBlockers.map((l) => l.key).join(", ")}`
|
|
27685
|
+
);
|
|
27636
27686
|
}
|
|
27637
27687
|
const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
|
|
27638
27688
|
for (const l of wontDoLinks) {
|
|
@@ -27694,9 +27744,11 @@ async function analyzeSingleArtifactComments(artifactId, title, jiraKey, jiraSta
|
|
|
27694
27744
|
const text = extractCommentText(c.body);
|
|
27695
27745
|
return `[${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
|
|
27696
27746
|
}).join("\n");
|
|
27697
|
-
promptParts.push(
|
|
27747
|
+
promptParts.push(
|
|
27748
|
+
`## ${artifactId} \u2014 ${title} (${jiraKey}, status: ${jiraStatus})
|
|
27698
27749
|
Comments:
|
|
27699
|
-
${commentTexts}`
|
|
27750
|
+
${commentTexts}`
|
|
27751
|
+
);
|
|
27700
27752
|
}
|
|
27701
27753
|
for (const signal of linkedIssueSignals) {
|
|
27702
27754
|
const linkedData = linkedJiraIssues.get(signal.sourceKey);
|
|
@@ -27785,7 +27837,7 @@ function buildAssessmentSummary(commentSummary, commentAnalysisProgress, signals
|
|
|
27785
27837
|
if (lastProgress) return lastProgress.proposedValue;
|
|
27786
27838
|
return c.marvinProgress;
|
|
27787
27839
|
});
|
|
27788
|
-
const childDoneCount = children.filter((c
|
|
27840
|
+
const childDoneCount = children.filter((c) => {
|
|
27789
27841
|
const updates = c.appliedUpdates.length > 0 ? c.appliedUpdates : c.proposedUpdates;
|
|
27790
27842
|
const lastStatus = findLast(updates, (u) => u.field === "status");
|
|
27791
27843
|
const effectiveStatus = lastStatus ? String(lastStatus.proposedValue) : c.marvinStatus;
|
|
@@ -27821,7 +27873,8 @@ function formatArtifactReport(report) {
|
|
|
27821
27873
|
parts.push(`## Jira State (${report.jiraKey})`);
|
|
27822
27874
|
const jiraParts = [`Status: ${report.jiraStatus ?? "unknown"}`];
|
|
27823
27875
|
if (report.jiraAssignee) jiraParts.push(`Assignee: ${report.jiraAssignee}`);
|
|
27824
|
-
if (report.jiraSubtaskProgress !== null)
|
|
27876
|
+
if (report.jiraSubtaskProgress !== null)
|
|
27877
|
+
jiraParts.push(`Subtask progress: ${report.jiraSubtaskProgress}%`);
|
|
27825
27878
|
parts.push(jiraParts.join(" | "));
|
|
27826
27879
|
if (report.statusDrift) {
|
|
27827
27880
|
parts.push(`\u26A0 Drift: ${report.marvinStatus} \u2192 ${report.proposedMarvinStatus}`);
|
|
@@ -27842,7 +27895,9 @@ function formatArtifactReport(report) {
|
|
|
27842
27895
|
if (report.totalBlockers > 0) {
|
|
27843
27896
|
parts.push(`## Blocker Resolution`);
|
|
27844
27897
|
const bpLabel = report.blockerProgress !== null ? `${report.blockerProgress}%` : "n/a (skipped)";
|
|
27845
|
-
parts.push(
|
|
27898
|
+
parts.push(
|
|
27899
|
+
` ${report.resolvedBlockers}/${report.totalBlockers} blockers resolved \u2192 ${bpLabel} prerequisite progress`
|
|
27900
|
+
);
|
|
27846
27901
|
parts.push("");
|
|
27847
27902
|
}
|
|
27848
27903
|
if (report.children.length > 0) {
|
|
@@ -27851,7 +27906,9 @@ function formatArtifactReport(report) {
|
|
|
27851
27906
|
report.children.reduce((s, c) => s + c.marvinProgress, 0) / report.children.length
|
|
27852
27907
|
);
|
|
27853
27908
|
const bar = progressBar6(childProgress);
|
|
27854
|
-
parts.push(
|
|
27909
|
+
parts.push(
|
|
27910
|
+
`## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`
|
|
27911
|
+
);
|
|
27855
27912
|
for (const child of report.children) {
|
|
27856
27913
|
formatArtifactChild(parts, child, 1);
|
|
27857
27914
|
}
|
|
@@ -27861,7 +27918,9 @@ function formatArtifactReport(report) {
|
|
|
27861
27918
|
parts.push(`## Linked Issues (${report.linkedIssues.length})`);
|
|
27862
27919
|
for (const link of report.linkedIssues) {
|
|
27863
27920
|
const doneMarker = link.isDone ? " \u2713" : "";
|
|
27864
|
-
parts.push(
|
|
27921
|
+
parts.push(
|
|
27922
|
+
` ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}`
|
|
27923
|
+
);
|
|
27865
27924
|
const signal = report.linkedIssueSignals.find((s) => s.sourceKey === link.key);
|
|
27866
27925
|
if (signal?.commentSummary) {
|
|
27867
27926
|
parts.push(` \u{1F4AC} ${signal.commentSummary}`);
|
|
@@ -27879,7 +27938,9 @@ function formatArtifactReport(report) {
|
|
|
27879
27938
|
if (report.proposedUpdates.length > 0) {
|
|
27880
27939
|
parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
|
|
27881
27940
|
for (const update of report.proposedUpdates) {
|
|
27882
|
-
parts.push(
|
|
27941
|
+
parts.push(
|
|
27942
|
+
` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
27943
|
+
);
|
|
27883
27944
|
parts.push(` Reason: ${update.reason}`);
|
|
27884
27945
|
}
|
|
27885
27946
|
parts.push("");
|
|
@@ -27889,7 +27950,9 @@ function formatArtifactReport(report) {
|
|
|
27889
27950
|
if (report.appliedUpdates.length > 0) {
|
|
27890
27951
|
parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
|
|
27891
27952
|
for (const update of report.appliedUpdates) {
|
|
27892
|
-
parts.push(
|
|
27953
|
+
parts.push(
|
|
27954
|
+
` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
27955
|
+
);
|
|
27893
27956
|
}
|
|
27894
27957
|
parts.push("");
|
|
27895
27958
|
}
|
|
@@ -27912,7 +27975,9 @@ function formatArtifactChild(parts, child, depth) {
|
|
|
27912
27975
|
if (s.startsWith("\u2705 No active")) continue;
|
|
27913
27976
|
signalHints.push(s);
|
|
27914
27977
|
}
|
|
27915
|
-
parts.push(
|
|
27978
|
+
parts.push(
|
|
27979
|
+
`${indent}${icon} ${child.artifactId} \u2014 ${child.title} [${child.marvinStatus}] ${child.marvinProgress}%${jiraLabel}${driftLabel}`
|
|
27980
|
+
);
|
|
27916
27981
|
if (child.commentSummary) {
|
|
27917
27982
|
parts.push(`${indent} \u{1F4AC} ${child.commentSummary}`);
|
|
27918
27983
|
}
|
|
@@ -28747,7 +28812,8 @@ function createJiraTools(store, projectConfig) {
|
|
|
28747
28812
|
{
|
|
28748
28813
|
artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'T-063', 'A-151', 'E-003')"),
|
|
28749
28814
|
applyUpdates: external_exports.boolean().optional().describe("Apply proposed status/progress updates to the artifact (default false)"),
|
|
28750
|
-
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.")
|
|
28815
|
+
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."),
|
|
28816
|
+
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).")
|
|
28751
28817
|
},
|
|
28752
28818
|
async (args) => {
|
|
28753
28819
|
const jira = createJiraClient(jiraUserConfig);
|
|
@@ -28760,6 +28826,7 @@ function createJiraTools(store, projectConfig) {
|
|
|
28760
28826
|
artifactId: args.artifactId,
|
|
28761
28827
|
applyUpdates: args.applyUpdates ?? false,
|
|
28762
28828
|
prerequisiteWeight: args.prerequisiteWeight,
|
|
28829
|
+
progressDivergenceThreshold: args.progressDivergenceThreshold,
|
|
28763
28830
|
statusMap
|
|
28764
28831
|
}
|
|
28765
28832
|
);
|
|
@@ -34428,7 +34495,7 @@ function createProgram() {
|
|
|
34428
34495
|
const program = new Command();
|
|
34429
34496
|
program.name("marvin").description(
|
|
34430
34497
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
34431
|
-
).version("0.5.
|
|
34498
|
+
).version("0.5.28");
|
|
34432
34499
|
program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
34433
34500
|
await initCommand();
|
|
34434
34501
|
});
|