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.js
CHANGED
|
@@ -26751,10 +26751,16 @@ function resolveWeight(complexity) {
|
|
|
26751
26751
|
function resolveProgress(frontmatter, commentAnalysisProgress) {
|
|
26752
26752
|
const hasExplicitProgress = "progress" in frontmatter && typeof frontmatter.progress === "number";
|
|
26753
26753
|
if (hasExplicitProgress) {
|
|
26754
|
-
return {
|
|
26754
|
+
return {
|
|
26755
|
+
progress: Math.max(0, Math.min(100, Math.round(frontmatter.progress))),
|
|
26756
|
+
progressSource: "explicit"
|
|
26757
|
+
};
|
|
26755
26758
|
}
|
|
26756
26759
|
if (commentAnalysisProgress !== null) {
|
|
26757
|
-
return {
|
|
26760
|
+
return {
|
|
26761
|
+
progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))),
|
|
26762
|
+
progressSource: "comment-analysis"
|
|
26763
|
+
};
|
|
26758
26764
|
}
|
|
26759
26765
|
const status = frontmatter.status;
|
|
26760
26766
|
const defaultProgress = STATUS_PROGRESS_DEFAULTS[status] ?? 0;
|
|
@@ -26779,7 +26785,13 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26779
26785
|
sprintId: options.sprintId ?? "unknown",
|
|
26780
26786
|
sprintTitle: "Sprint not found",
|
|
26781
26787
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26782
|
-
timeline: {
|
|
26788
|
+
timeline: {
|
|
26789
|
+
startDate: null,
|
|
26790
|
+
endDate: null,
|
|
26791
|
+
daysRemaining: 0,
|
|
26792
|
+
totalDays: 0,
|
|
26793
|
+
percentComplete: 0
|
|
26794
|
+
},
|
|
26783
26795
|
overallProgress: 0,
|
|
26784
26796
|
itemReports: [],
|
|
26785
26797
|
focusAreas: [],
|
|
@@ -26787,7 +26799,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26787
26799
|
blockers: [],
|
|
26788
26800
|
proposedUpdates: [],
|
|
26789
26801
|
appliedUpdates: [],
|
|
26790
|
-
errors: [
|
|
26802
|
+
errors: [
|
|
26803
|
+
`Sprint ${options.sprintId ?? "(active)"} not found. Create a sprint artifact first.`
|
|
26804
|
+
]
|
|
26791
26805
|
};
|
|
26792
26806
|
}
|
|
26793
26807
|
const sprintTag = `sprint:${sprintData.sprint.id}`;
|
|
@@ -26831,7 +26845,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26831
26845
|
});
|
|
26832
26846
|
} else {
|
|
26833
26847
|
const batchKey = batch[results.indexOf(result)];
|
|
26834
|
-
errors.push(
|
|
26848
|
+
errors.push(
|
|
26849
|
+
`Failed to fetch ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
|
|
26850
|
+
);
|
|
26835
26851
|
}
|
|
26836
26852
|
}
|
|
26837
26853
|
}
|
|
@@ -26873,12 +26889,16 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26873
26889
|
}
|
|
26874
26890
|
} else {
|
|
26875
26891
|
const batchKey = batch[results.indexOf(result)];
|
|
26876
|
-
errors.push(
|
|
26892
|
+
errors.push(
|
|
26893
|
+
`Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
|
|
26894
|
+
);
|
|
26877
26895
|
}
|
|
26878
26896
|
}
|
|
26879
26897
|
}
|
|
26880
26898
|
if (queue.length > 0) {
|
|
26881
|
-
errors.push(
|
|
26899
|
+
errors.push(
|
|
26900
|
+
`Link traversal capped at ${MAX_LINKED_ISSUES} linked issues (${queue.length} remaining undiscovered)`
|
|
26901
|
+
);
|
|
26882
26902
|
}
|
|
26883
26903
|
}
|
|
26884
26904
|
const proposedUpdates = [];
|
|
@@ -26956,12 +26976,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26956
26976
|
);
|
|
26957
26977
|
itemLinkedIssues = allLinks;
|
|
26958
26978
|
itemLinkedIssueSignals.push(...allSignals);
|
|
26959
|
-
analyzeLinkedIssueSignals(
|
|
26960
|
-
allLinks,
|
|
26961
|
-
fm,
|
|
26962
|
-
jiraKey,
|
|
26963
|
-
proposedUpdates
|
|
26964
|
-
);
|
|
26979
|
+
analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
|
|
26965
26980
|
}
|
|
26966
26981
|
const report = {
|
|
26967
26982
|
id: fm.id,
|
|
@@ -27080,10 +27095,7 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
27080
27095
|
}
|
|
27081
27096
|
if (options.traverseLinks) {
|
|
27082
27097
|
try {
|
|
27083
|
-
const linkedSummaries = await analyzeLinkedIssueComments(
|
|
27084
|
-
itemReports,
|
|
27085
|
-
linkedJiraIssues
|
|
27086
|
-
);
|
|
27098
|
+
const linkedSummaries = await analyzeLinkedIssueComments(itemReports, linkedJiraIssues);
|
|
27087
27099
|
for (const [artifactId, signalSummaries] of linkedSummaries) {
|
|
27088
27100
|
const report = itemReports.find((r) => r.id === artifactId);
|
|
27089
27101
|
if (!report) continue;
|
|
@@ -27095,7 +27107,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
27095
27107
|
}
|
|
27096
27108
|
}
|
|
27097
27109
|
} catch (err) {
|
|
27098
|
-
errors.push(
|
|
27110
|
+
errors.push(
|
|
27111
|
+
`Linked issue comment analysis failed: ${err instanceof Error ? err.message : String(err)}`
|
|
27112
|
+
);
|
|
27099
27113
|
}
|
|
27100
27114
|
}
|
|
27101
27115
|
}
|
|
@@ -27167,9 +27181,11 @@ async function analyzeCommentsForProgress(items, jiraIssues, itemJiraKeys) {
|
|
|
27167
27181
|
const text = extractCommentText(c.body);
|
|
27168
27182
|
return ` [${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
|
|
27169
27183
|
}).join("\n");
|
|
27170
|
-
promptParts.push(
|
|
27184
|
+
promptParts.push(
|
|
27185
|
+
`## ${item.id} \u2014 ${item.title} (${jiraKey}, Jira status: ${item.jiraStatus})
|
|
27171
27186
|
Comments:
|
|
27172
|
-
${commentTexts}`
|
|
27187
|
+
${commentTexts}`
|
|
27188
|
+
);
|
|
27173
27189
|
}
|
|
27174
27190
|
if (promptParts.length === 0) return summaries;
|
|
27175
27191
|
const prompt = promptParts.join("\n\n");
|
|
@@ -27242,7 +27258,9 @@ function collectTransitiveLinks(primaryIssue, primaryIssues, linkedJiraIssues) {
|
|
|
27242
27258
|
commentSummary: null
|
|
27243
27259
|
});
|
|
27244
27260
|
}
|
|
27245
|
-
const nextLinks = collectLinkedIssues(linkedData.issue).filter(
|
|
27261
|
+
const nextLinks = collectLinkedIssues(linkedData.issue).filter(
|
|
27262
|
+
(l) => l.relationship !== "subtask" && !visited.has(l.key)
|
|
27263
|
+
);
|
|
27246
27264
|
for (const next of nextLinks) {
|
|
27247
27265
|
visited.add(next.key);
|
|
27248
27266
|
queue.push(next);
|
|
@@ -27266,9 +27284,7 @@ function analyzeLinkedIssueSignals(linkedIssues, frontmatter, jiraKey, proposedU
|
|
|
27266
27284
|
reason: `All blocking issues resolved: ${blockerLinks.map((l) => l.key).join(", ")}`
|
|
27267
27285
|
});
|
|
27268
27286
|
}
|
|
27269
|
-
const wontDoLinks = linkedIssues.filter(
|
|
27270
|
-
(l) => WONT_DO_STATUSES.has(l.status.toLowerCase())
|
|
27271
|
-
);
|
|
27287
|
+
const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
|
|
27272
27288
|
if (wontDoLinks.length > 0) {
|
|
27273
27289
|
proposedUpdates.push({
|
|
27274
27290
|
artifactId: frontmatter.id,
|
|
@@ -27342,7 +27358,9 @@ ${linkedParts.join("\n")}`);
|
|
|
27342
27358
|
for (const [artifactId, linkedSummaries] of Object.entries(parsed)) {
|
|
27343
27359
|
if (typeof linkedSummaries === "object" && linkedSummaries !== null) {
|
|
27344
27360
|
const signalMap = /* @__PURE__ */ new Map();
|
|
27345
|
-
for (const [key, summary] of Object.entries(
|
|
27361
|
+
for (const [key, summary] of Object.entries(
|
|
27362
|
+
linkedSummaries
|
|
27363
|
+
)) {
|
|
27346
27364
|
if (typeof summary === "string") {
|
|
27347
27365
|
signalMap.set(key, summary);
|
|
27348
27366
|
}
|
|
@@ -27381,7 +27399,9 @@ function formatProgressReport(report) {
|
|
|
27381
27399
|
if (report.timeline.startDate && report.timeline.endDate) {
|
|
27382
27400
|
parts.push(`## Timeline`);
|
|
27383
27401
|
parts.push(`${report.timeline.startDate} \u2192 ${report.timeline.endDate}`);
|
|
27384
|
-
parts.push(
|
|
27402
|
+
parts.push(
|
|
27403
|
+
`Days remaining: ${report.timeline.daysRemaining} / ${report.timeline.totalDays} (${report.timeline.percentComplete}% elapsed)`
|
|
27404
|
+
);
|
|
27385
27405
|
parts.push(`Overall progress: ${report.overallProgress}%`);
|
|
27386
27406
|
parts.push("");
|
|
27387
27407
|
}
|
|
@@ -27391,7 +27411,9 @@ function formatProgressReport(report) {
|
|
|
27391
27411
|
for (const area of report.focusAreas) {
|
|
27392
27412
|
const bar = progressBar6(area.progress);
|
|
27393
27413
|
parts.push(`### ${area.name} ${bar} ${area.progress}%`);
|
|
27394
|
-
parts.push(
|
|
27414
|
+
parts.push(
|
|
27415
|
+
`${area.doneCount}/${area.taskCount} done${area.blockedCount > 0 ? ` | ${area.blockedCount} blocked` : ""}`
|
|
27416
|
+
);
|
|
27395
27417
|
if (area.riskWarning) {
|
|
27396
27418
|
parts.push(` \u26A0 ${area.riskWarning}`);
|
|
27397
27419
|
}
|
|
@@ -27430,7 +27452,9 @@ function formatProgressReport(report) {
|
|
|
27430
27452
|
if (report.proposedUpdates.length > 0) {
|
|
27431
27453
|
parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
|
|
27432
27454
|
for (const update of report.proposedUpdates) {
|
|
27433
|
-
parts.push(
|
|
27455
|
+
parts.push(
|
|
27456
|
+
` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
27457
|
+
);
|
|
27434
27458
|
parts.push(` Reason: ${update.reason}`);
|
|
27435
27459
|
}
|
|
27436
27460
|
parts.push("");
|
|
@@ -27440,7 +27464,9 @@ function formatProgressReport(report) {
|
|
|
27440
27464
|
if (report.appliedUpdates.length > 0) {
|
|
27441
27465
|
parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
|
|
27442
27466
|
for (const update of report.appliedUpdates) {
|
|
27443
|
-
parts.push(
|
|
27467
|
+
parts.push(
|
|
27468
|
+
` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
27469
|
+
);
|
|
27444
27470
|
}
|
|
27445
27471
|
parts.push("");
|
|
27446
27472
|
}
|
|
@@ -27461,7 +27487,9 @@ function formatItemLine(parts, item, depth) {
|
|
|
27461
27487
|
const progressLabel = ` ${item.progress}%`;
|
|
27462
27488
|
const weightLabel = `w${item.weight}`;
|
|
27463
27489
|
const sourceLabel = item.progressSource === "explicit" ? "" : item.progressSource === "comment-analysis" ? " (llm)" : " (est)";
|
|
27464
|
-
parts.push(
|
|
27490
|
+
parts.push(
|
|
27491
|
+
`${indent}${statusIcon} ${item.id} \u2014 ${item.title} [${item.marvinStatus}]${progressLabel}${sourceLabel} (${weightLabel})${jiraLabel}${driftFlag}`
|
|
27492
|
+
);
|
|
27465
27493
|
if (item.commentSummary) {
|
|
27466
27494
|
parts.push(`${indent} \u{1F4AC} ${item.commentSummary}`);
|
|
27467
27495
|
}
|
|
@@ -27471,7 +27499,9 @@ function formatItemLine(parts, item, depth) {
|
|
|
27471
27499
|
const doneMarker = link.isDone ? " \u2713" : "";
|
|
27472
27500
|
const blockerResolved = link.isDone && BLOCKER_LINK_PATTERNS.some((p) => link.relationship.toLowerCase().includes(p.split(" ")[0])) ? " unblock signal" : "";
|
|
27473
27501
|
const wontDo = WONT_DO_STATUSES.has(link.status.toLowerCase()) ? " \u26A0 needs review" : "";
|
|
27474
|
-
parts.push(
|
|
27502
|
+
parts.push(
|
|
27503
|
+
`${indent} ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}${blockerResolved}${wontDo}`
|
|
27504
|
+
);
|
|
27475
27505
|
const signal = item.linkedIssueSignals.find((s) => s.sourceKey === link.key);
|
|
27476
27506
|
if (signal?.commentSummary) {
|
|
27477
27507
|
parts.push(`${indent} \u{1F4AC} ${signal.commentSummary}`);
|
|
@@ -27490,6 +27520,7 @@ function progressBar6(pct) {
|
|
|
27490
27520
|
var MAX_ARTIFACT_NODES = 50;
|
|
27491
27521
|
var MAX_LLM_DEPTH = 3;
|
|
27492
27522
|
var MAX_LLM_COMMENT_CHARS = 8e3;
|
|
27523
|
+
var DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD = 15;
|
|
27493
27524
|
async function assessArtifact(store, client, host, options) {
|
|
27494
27525
|
const visited = /* @__PURE__ */ new Set();
|
|
27495
27526
|
return _assessArtifactRecursive(store, client, host, options, visited, 0);
|
|
@@ -27497,10 +27528,14 @@ async function assessArtifact(store, client, host, options) {
|
|
|
27497
27528
|
async function _assessArtifactRecursive(store, client, host, options, visited, depth) {
|
|
27498
27529
|
const errors = [];
|
|
27499
27530
|
if (visited.has(options.artifactId)) {
|
|
27500
|
-
return emptyArtifactReport(options.artifactId, [
|
|
27531
|
+
return emptyArtifactReport(options.artifactId, [
|
|
27532
|
+
`Cycle detected: ${options.artifactId} already visited`
|
|
27533
|
+
]);
|
|
27501
27534
|
}
|
|
27502
27535
|
if (visited.size >= MAX_ARTIFACT_NODES) {
|
|
27503
|
-
return emptyArtifactReport(options.artifactId, [
|
|
27536
|
+
return emptyArtifactReport(options.artifactId, [
|
|
27537
|
+
`Node cap reached (${MAX_ARTIFACT_NODES}), skipping ${options.artifactId}`
|
|
27538
|
+
]);
|
|
27504
27539
|
}
|
|
27505
27540
|
visited.add(options.artifactId);
|
|
27506
27541
|
const doc = store.get(options.artifactId);
|
|
@@ -27570,27 +27605,29 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27570
27605
|
if (result.status === "fulfilled") {
|
|
27571
27606
|
const { key, issue: li, comments: lc } = result.value;
|
|
27572
27607
|
linkedJiraIssues.set(key, { issue: li, comments: lc });
|
|
27573
|
-
const newLinks = collectLinkedIssues(li).filter(
|
|
27608
|
+
const newLinks = collectLinkedIssues(li).filter(
|
|
27609
|
+
(l) => l.relationship !== "subtask" && !jiraVisited.has(l.key)
|
|
27610
|
+
);
|
|
27574
27611
|
for (const nl of newLinks) {
|
|
27575
27612
|
jiraVisited.add(nl.key);
|
|
27576
27613
|
queue.push(nl.key);
|
|
27577
27614
|
}
|
|
27578
27615
|
} else {
|
|
27579
27616
|
const batchKey = batch[results.indexOf(result)];
|
|
27580
|
-
errors.push(
|
|
27617
|
+
errors.push(
|
|
27618
|
+
`Failed to fetch linked issue ${batchKey}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`
|
|
27619
|
+
);
|
|
27581
27620
|
}
|
|
27582
27621
|
}
|
|
27583
27622
|
}
|
|
27584
|
-
const { allLinks, allSignals } = collectTransitiveLinks(
|
|
27585
|
-
issue2,
|
|
27586
|
-
jiraIssues,
|
|
27587
|
-
linkedJiraIssues
|
|
27588
|
-
);
|
|
27623
|
+
const { allLinks, allSignals } = collectTransitiveLinks(issue2, jiraIssues, linkedJiraIssues);
|
|
27589
27624
|
linkedIssues = allLinks;
|
|
27590
27625
|
linkedIssueSignals = allSignals;
|
|
27591
27626
|
analyzeLinkedIssueSignals(allLinks, fm, jiraKey, proposedUpdates);
|
|
27592
27627
|
} catch (err) {
|
|
27593
|
-
errors.push(
|
|
27628
|
+
errors.push(
|
|
27629
|
+
`Failed to fetch ${jiraKey}: ${err instanceof Error ? err.message : String(err)}`
|
|
27630
|
+
);
|
|
27594
27631
|
}
|
|
27595
27632
|
}
|
|
27596
27633
|
const currentProgress = getEffectiveProgress(fm);
|
|
@@ -27631,7 +27668,11 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27631
27668
|
const primaryHasComments = jiraKey ? (jiraIssues.get(jiraKey)?.comments.length ?? 0) > 0 : false;
|
|
27632
27669
|
let commentAnalysisProgress = null;
|
|
27633
27670
|
if (depth < MAX_LLM_DEPTH && jiraKey && primaryHasComments) {
|
|
27634
|
-
const estimatedChars = estimateCommentTextSize(
|
|
27671
|
+
const estimatedChars = estimateCommentTextSize(
|
|
27672
|
+
jiraIssues,
|
|
27673
|
+
linkedJiraIssues,
|
|
27674
|
+
linkedIssueSignals
|
|
27675
|
+
);
|
|
27635
27676
|
if (estimatedChars <= MAX_LLM_COMMENT_CHARS) {
|
|
27636
27677
|
try {
|
|
27637
27678
|
const analysis = await analyzeSingleArtifactComments(
|
|
@@ -27646,14 +27687,16 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27646
27687
|
commentSummary = analysis.summary;
|
|
27647
27688
|
commentAnalysisProgress = analysis.progressEstimate;
|
|
27648
27689
|
if (commentAnalysisProgress !== null) {
|
|
27649
|
-
const
|
|
27650
|
-
|
|
27690
|
+
const divergence = Math.abs(commentAnalysisProgress - currentProgress);
|
|
27691
|
+
const threshold = options.progressDivergenceThreshold ?? DEFAULT_PROGRESS_DIVERGENCE_THRESHOLD;
|
|
27692
|
+
if (divergence >= threshold && commentAnalysisProgress !== currentProgress) {
|
|
27693
|
+
const overrideWarning = fm.progressOverride ? " \u26A0 progressOverride is set \u2014 review before applying" : "";
|
|
27651
27694
|
proposedUpdates.push({
|
|
27652
27695
|
artifactId: fm.id,
|
|
27653
27696
|
field: "progress",
|
|
27654
27697
|
currentValue: currentProgress,
|
|
27655
27698
|
proposedValue: commentAnalysisProgress,
|
|
27656
|
-
reason: `Comment
|
|
27699
|
+
reason: `Comment-derived estimate (${commentAnalysisProgress}%) diverges from current (${currentProgress}%) by ${divergence}pp${overrideWarning}`
|
|
27657
27700
|
});
|
|
27658
27701
|
}
|
|
27659
27702
|
}
|
|
@@ -27666,7 +27709,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27666
27709
|
const children = [];
|
|
27667
27710
|
for (const childId of childIds) {
|
|
27668
27711
|
if (visited.size >= MAX_ARTIFACT_NODES) {
|
|
27669
|
-
errors.push(
|
|
27712
|
+
errors.push(
|
|
27713
|
+
`Node cap reached (${MAX_ARTIFACT_NODES}), ${childIds.length - children.length} children skipped`
|
|
27714
|
+
);
|
|
27670
27715
|
break;
|
|
27671
27716
|
}
|
|
27672
27717
|
const childReport = await _assessArtifactRecursive(
|
|
@@ -27710,7 +27755,10 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27710
27755
|
blockerProgressValue = blockerResult.blockerProgress;
|
|
27711
27756
|
totalBlockersCount = blockerResult.totalBlockers;
|
|
27712
27757
|
resolvedBlockersCount = blockerResult.resolvedBlockers;
|
|
27713
|
-
const lastProgressUpdate = findLast(
|
|
27758
|
+
const lastProgressUpdate = findLast(
|
|
27759
|
+
proposedUpdates,
|
|
27760
|
+
(u) => u.artifactId === fm.id && u.field === "progress"
|
|
27761
|
+
);
|
|
27714
27762
|
const implementationProgress = lastProgressUpdate ? lastProgressUpdate.proposedValue : currentProgress;
|
|
27715
27763
|
const combinedProgress = Math.round(
|
|
27716
27764
|
blockerResult.blockerProgress + implementationProgress * (1 - prerequisiteWeight)
|
|
@@ -27805,7 +27853,9 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
|
|
|
27805
27853
|
}
|
|
27806
27854
|
store.update(fm.id, payload);
|
|
27807
27855
|
} catch (err) {
|
|
27808
|
-
errors.push(
|
|
27856
|
+
errors.push(
|
|
27857
|
+
`Failed to persist assessment history: ${err instanceof Error ? err.message : String(err)}`
|
|
27858
|
+
);
|
|
27809
27859
|
}
|
|
27810
27860
|
}
|
|
27811
27861
|
return {
|
|
@@ -27865,9 +27915,7 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
|
|
|
27865
27915
|
signals.push(`\u{1F6AB} Blocker \u2014 "${s.snippet}"`);
|
|
27866
27916
|
}
|
|
27867
27917
|
}
|
|
27868
|
-
const blockingLinks = linkedIssues.filter(
|
|
27869
|
-
(l) => l.relationship.toLowerCase().includes("block")
|
|
27870
|
-
);
|
|
27918
|
+
const blockingLinks = linkedIssues.filter((l) => l.relationship.toLowerCase().includes("block"));
|
|
27871
27919
|
const activeBlockers = blockingLinks.filter((l) => !l.isDone);
|
|
27872
27920
|
const resolvedBlockers = blockingLinks.filter((l) => l.isDone);
|
|
27873
27921
|
if (activeBlockers.length > 0) {
|
|
@@ -27876,7 +27924,9 @@ function buildSignals(commentSignals, linkedIssues, statusDrift, proposedStatus)
|
|
|
27876
27924
|
}
|
|
27877
27925
|
}
|
|
27878
27926
|
if (resolvedBlockers.length > 0 && activeBlockers.length === 0) {
|
|
27879
|
-
signals.push(
|
|
27927
|
+
signals.push(
|
|
27928
|
+
`\u2705 Unblocked \u2014 all blocking issues resolved: ${resolvedBlockers.map((l) => l.key).join(", ")}`
|
|
27929
|
+
);
|
|
27880
27930
|
}
|
|
27881
27931
|
const wontDoLinks = linkedIssues.filter((l) => WONT_DO_STATUSES.has(l.status.toLowerCase()));
|
|
27882
27932
|
for (const l of wontDoLinks) {
|
|
@@ -27938,9 +27988,11 @@ async function analyzeSingleArtifactComments(artifactId, title, jiraKey, jiraSta
|
|
|
27938
27988
|
const text = extractCommentText(c.body);
|
|
27939
27989
|
return `[${c.author.displayName}, ${c.created.slice(0, 10)}]: ${text.slice(0, 500)}`;
|
|
27940
27990
|
}).join("\n");
|
|
27941
|
-
promptParts.push(
|
|
27991
|
+
promptParts.push(
|
|
27992
|
+
`## ${artifactId} \u2014 ${title} (${jiraKey}, status: ${jiraStatus})
|
|
27942
27993
|
Comments:
|
|
27943
|
-
${commentTexts}`
|
|
27994
|
+
${commentTexts}`
|
|
27995
|
+
);
|
|
27944
27996
|
}
|
|
27945
27997
|
for (const signal of linkedIssueSignals) {
|
|
27946
27998
|
const linkedData = linkedJiraIssues.get(signal.sourceKey);
|
|
@@ -28029,7 +28081,7 @@ function buildAssessmentSummary(commentSummary, commentAnalysisProgress, signals
|
|
|
28029
28081
|
if (lastProgress) return lastProgress.proposedValue;
|
|
28030
28082
|
return c.marvinProgress;
|
|
28031
28083
|
});
|
|
28032
|
-
const childDoneCount = children.filter((c
|
|
28084
|
+
const childDoneCount = children.filter((c) => {
|
|
28033
28085
|
const updates = c.appliedUpdates.length > 0 ? c.appliedUpdates : c.proposedUpdates;
|
|
28034
28086
|
const lastStatus = findLast(updates, (u) => u.field === "status");
|
|
28035
28087
|
const effectiveStatus = lastStatus ? String(lastStatus.proposedValue) : c.marvinStatus;
|
|
@@ -28065,7 +28117,8 @@ function formatArtifactReport(report) {
|
|
|
28065
28117
|
parts.push(`## Jira State (${report.jiraKey})`);
|
|
28066
28118
|
const jiraParts = [`Status: ${report.jiraStatus ?? "unknown"}`];
|
|
28067
28119
|
if (report.jiraAssignee) jiraParts.push(`Assignee: ${report.jiraAssignee}`);
|
|
28068
|
-
if (report.jiraSubtaskProgress !== null)
|
|
28120
|
+
if (report.jiraSubtaskProgress !== null)
|
|
28121
|
+
jiraParts.push(`Subtask progress: ${report.jiraSubtaskProgress}%`);
|
|
28069
28122
|
parts.push(jiraParts.join(" | "));
|
|
28070
28123
|
if (report.statusDrift) {
|
|
28071
28124
|
parts.push(`\u26A0 Drift: ${report.marvinStatus} \u2192 ${report.proposedMarvinStatus}`);
|
|
@@ -28086,7 +28139,9 @@ function formatArtifactReport(report) {
|
|
|
28086
28139
|
if (report.totalBlockers > 0) {
|
|
28087
28140
|
parts.push(`## Blocker Resolution`);
|
|
28088
28141
|
const bpLabel = report.blockerProgress !== null ? `${report.blockerProgress}%` : "n/a (skipped)";
|
|
28089
|
-
parts.push(
|
|
28142
|
+
parts.push(
|
|
28143
|
+
` ${report.resolvedBlockers}/${report.totalBlockers} blockers resolved \u2192 ${bpLabel} prerequisite progress`
|
|
28144
|
+
);
|
|
28090
28145
|
parts.push("");
|
|
28091
28146
|
}
|
|
28092
28147
|
if (report.children.length > 0) {
|
|
@@ -28095,7 +28150,9 @@ function formatArtifactReport(report) {
|
|
|
28095
28150
|
report.children.reduce((s, c) => s + c.marvinProgress, 0) / report.children.length
|
|
28096
28151
|
);
|
|
28097
28152
|
const bar = progressBar6(childProgress);
|
|
28098
|
-
parts.push(
|
|
28153
|
+
parts.push(
|
|
28154
|
+
`## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`
|
|
28155
|
+
);
|
|
28099
28156
|
for (const child of report.children) {
|
|
28100
28157
|
formatArtifactChild(parts, child, 1);
|
|
28101
28158
|
}
|
|
@@ -28105,7 +28162,9 @@ function formatArtifactReport(report) {
|
|
|
28105
28162
|
parts.push(`## Linked Issues (${report.linkedIssues.length})`);
|
|
28106
28163
|
for (const link of report.linkedIssues) {
|
|
28107
28164
|
const doneMarker = link.isDone ? " \u2713" : "";
|
|
28108
|
-
parts.push(
|
|
28165
|
+
parts.push(
|
|
28166
|
+
` ${link.relationship} ${link.key} "${link.summary}" [${link.status}]${doneMarker}`
|
|
28167
|
+
);
|
|
28109
28168
|
const signal = report.linkedIssueSignals.find((s) => s.sourceKey === link.key);
|
|
28110
28169
|
if (signal?.commentSummary) {
|
|
28111
28170
|
parts.push(` \u{1F4AC} ${signal.commentSummary}`);
|
|
@@ -28123,7 +28182,9 @@ function formatArtifactReport(report) {
|
|
|
28123
28182
|
if (report.proposedUpdates.length > 0) {
|
|
28124
28183
|
parts.push(`## Proposed Updates (${report.proposedUpdates.length})`);
|
|
28125
28184
|
for (const update of report.proposedUpdates) {
|
|
28126
|
-
parts.push(
|
|
28185
|
+
parts.push(
|
|
28186
|
+
` ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
28187
|
+
);
|
|
28127
28188
|
parts.push(` Reason: ${update.reason}`);
|
|
28128
28189
|
}
|
|
28129
28190
|
parts.push("");
|
|
@@ -28133,7 +28194,9 @@ function formatArtifactReport(report) {
|
|
|
28133
28194
|
if (report.appliedUpdates.length > 0) {
|
|
28134
28195
|
parts.push(`## Applied Updates (${report.appliedUpdates.length})`);
|
|
28135
28196
|
for (const update of report.appliedUpdates) {
|
|
28136
|
-
parts.push(
|
|
28197
|
+
parts.push(
|
|
28198
|
+
` \u2713 ${update.artifactId}.${update.field}: ${String(update.currentValue)} \u2192 ${String(update.proposedValue)}`
|
|
28199
|
+
);
|
|
28137
28200
|
}
|
|
28138
28201
|
parts.push("");
|
|
28139
28202
|
}
|
|
@@ -28156,7 +28219,9 @@ function formatArtifactChild(parts, child, depth) {
|
|
|
28156
28219
|
if (s.startsWith("\u2705 No active")) continue;
|
|
28157
28220
|
signalHints.push(s);
|
|
28158
28221
|
}
|
|
28159
|
-
parts.push(
|
|
28222
|
+
parts.push(
|
|
28223
|
+
`${indent}${icon} ${child.artifactId} \u2014 ${child.title} [${child.marvinStatus}] ${child.marvinProgress}%${jiraLabel}${driftLabel}`
|
|
28224
|
+
);
|
|
28160
28225
|
if (child.commentSummary) {
|
|
28161
28226
|
parts.push(`${indent} \u{1F4AC} ${child.commentSummary}`);
|
|
28162
28227
|
}
|
|
@@ -28991,7 +29056,8 @@ function createJiraTools(store, projectConfig) {
|
|
|
28991
29056
|
{
|
|
28992
29057
|
artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'T-063', 'A-151', 'E-003')"),
|
|
28993
29058
|
applyUpdates: external_exports.boolean().optional().describe("Apply proposed status/progress updates to the artifact (default false)"),
|
|
28994
|
-
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.")
|
|
29059
|
+
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."),
|
|
29060
|
+
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).")
|
|
28995
29061
|
},
|
|
28996
29062
|
async (args) => {
|
|
28997
29063
|
const jira = createJiraClient(jiraUserConfig);
|
|
@@ -29004,6 +29070,7 @@ function createJiraTools(store, projectConfig) {
|
|
|
29004
29070
|
artifactId: args.artifactId,
|
|
29005
29071
|
applyUpdates: args.applyUpdates ?? false,
|
|
29006
29072
|
prerequisiteWeight: args.prerequisiteWeight,
|
|
29073
|
+
progressDivergenceThreshold: args.progressDivergenceThreshold,
|
|
29007
29074
|
statusMap
|
|
29008
29075
|
}
|
|
29009
29076
|
);
|
|
@@ -34418,7 +34485,7 @@ function createProgram() {
|
|
|
34418
34485
|
const program2 = new Command();
|
|
34419
34486
|
program2.name("marvin").description(
|
|
34420
34487
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
34421
|
-
).version("0.5.
|
|
34488
|
+
).version("0.5.28");
|
|
34422
34489
|
program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
34423
34490
|
await initCommand();
|
|
34424
34491
|
});
|