mrvn-cli 0.5.21 → 0.5.23

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/marvin.js CHANGED
@@ -26053,6 +26053,7 @@ function generateProposedActions(issues) {
26053
26053
  // src/skills/builtin/jira/sprint-progress.ts
26054
26054
  import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
26055
26055
  var DONE_STATUSES15 = /* @__PURE__ */ new Set(["done", "closed", "resolved", "obsolete", "wont do", "cancelled"]);
26056
+ var PROGRESS_DONE_STATUSES = /* @__PURE__ */ new Set(["done", "closed", "resolved", "obsolete", "wont do"]);
26056
26057
  var BATCH_SIZE = 5;
26057
26058
  var MAX_LINKED_ISSUES = 50;
26058
26059
  var BLOCKED_WEIGHT_RISK_THRESHOLD = 0.3;
@@ -26942,11 +26943,12 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
26942
26943
  }
26943
26944
  }
26944
26945
  const primaryHasComments = jiraKey ? (jiraIssues.get(jiraKey)?.comments.length ?? 0) > 0 : false;
26946
+ let commentAnalysisProgress = null;
26945
26947
  if (depth < MAX_LLM_DEPTH && jiraKey && primaryHasComments) {
26946
26948
  const estimatedChars = estimateCommentTextSize(jiraIssues, linkedJiraIssues, linkedIssueSignals);
26947
26949
  if (estimatedChars <= MAX_LLM_COMMENT_CHARS) {
26948
26950
  try {
26949
- const summary = await analyzeSingleArtifactComments(
26951
+ const analysis = await analyzeSingleArtifactComments(
26950
26952
  fm.id,
26951
26953
  fm.title,
26952
26954
  jiraKey,
@@ -26955,7 +26957,20 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
26955
26957
  linkedJiraIssues,
26956
26958
  linkedIssueSignals
26957
26959
  );
26958
- commentSummary = summary;
26960
+ commentSummary = analysis.summary;
26961
+ commentAnalysisProgress = analysis.progressEstimate;
26962
+ if (commentAnalysisProgress !== null) {
26963
+ const hasExplicitProgress = "progress" in fm && typeof fm.progress === "number";
26964
+ if (!hasExplicitProgress && !fm.progressOverride && commentAnalysisProgress !== currentProgress) {
26965
+ proposedUpdates.push({
26966
+ artifactId: fm.id,
26967
+ field: "progress",
26968
+ currentValue: currentProgress,
26969
+ proposedValue: commentAnalysisProgress,
26970
+ reason: `Comment analysis estimates ${commentAnalysisProgress}% progress`
26971
+ });
26972
+ }
26973
+ }
26959
26974
  } catch (err) {
26960
26975
  errors.push(`Comment analysis failed: ${err instanceof Error ? err.message : String(err)}`);
26961
26976
  }
@@ -26979,8 +26994,16 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
26979
26994
  children.push(childReport);
26980
26995
  }
26981
26996
  if (children.length > 0) {
26997
+ const childProgressValues = children.map((c) => {
26998
+ const updates = c.appliedUpdates.length > 0 ? c.appliedUpdates : c.proposedUpdates;
26999
+ const lastStatus = findLast(updates, (u) => u.field === "status");
27000
+ if (lastStatus && PROGRESS_DONE_STATUSES.has(String(lastStatus.proposedValue))) return 100;
27001
+ const lastProgress = findLast(updates, (u) => u.field === "progress");
27002
+ if (lastProgress) return lastProgress.proposedValue;
27003
+ return c.marvinProgress;
27004
+ });
26982
27005
  const rolledUpProgress = Math.round(
26983
- children.reduce((s, c) => s + c.marvinProgress, 0) / children.length
27006
+ childProgressValues.reduce((s, p) => s + p, 0) / childProgressValues.length
26984
27007
  );
26985
27008
  if (rolledUpProgress !== currentProgress) {
26986
27009
  proposedUpdates.push({
@@ -26996,7 +27019,7 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
26996
27019
  const appliedUpdates = [];
26997
27020
  if (options.applyUpdates && proposedUpdates.length > 0) {
26998
27021
  const doneArtifacts = new Set(
26999
- proposedUpdates.filter((u) => u.field === "status" && DONE_STATUSES15.has(String(u.proposedValue))).map((u) => u.artifactId)
27022
+ proposedUpdates.filter((u) => u.field === "status" && PROGRESS_DONE_STATUSES.has(String(u.proposedValue))).map((u) => u.artifactId)
27000
27023
  );
27001
27024
  for (const update of proposedUpdates) {
27002
27025
  if (update.field === "review") continue;
@@ -27045,6 +27068,7 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
27045
27068
  progressDrift,
27046
27069
  commentSignals,
27047
27070
  commentSummary,
27071
+ commentAnalysisProgress,
27048
27072
  linkedIssues,
27049
27073
  linkedIssueSignals,
27050
27074
  children,
@@ -27137,13 +27161,15 @@ function estimateCommentTextSize(jiraIssues, linkedJiraIssues, linkedIssueSignal
27137
27161
  }
27138
27162
  var SINGLE_ARTIFACT_COMMENT_PROMPT = `You are a delivery management assistant analyzing Jira comments for a single work item.
27139
27163
 
27140
- Produce a 2-3 sentence progress summary covering:
27141
- - What work has been completed
27142
- - What is pending or blocked
27143
- - Any decisions, handoffs, or scheduling mentioned
27144
- - Relevant context from linked issue comments (if provided)
27164
+ Analyze the comments and produce:
27165
+ 1. A 2-3 sentence progress summary covering: what work has been completed, what is pending or blocked, any decisions/handoffs/scheduling mentioned, and relevant context from linked issue comments (if provided).
27166
+ 2. A progress estimate (0-100%) based on evidence in the comments \u2014 e.g., if comments indicate all items have been triaged into tasks, or implementation is complete pending review, estimate accordingly. If you cannot determine progress from the comments, set progressEstimate to null.
27145
27167
 
27146
- Return ONLY the summary text, no JSON or formatting.`;
27168
+ Return a JSON object with this exact structure:
27169
+ {"summary": "your 2-3 sentence summary", "progressEstimate": 75}
27170
+
27171
+ Use null for progressEstimate if the comments don't provide enough evidence to estimate.
27172
+ IMPORTANT: Only return the JSON object, no other text.`;
27147
27173
  async function analyzeSingleArtifactComments(artifactId, title, jiraKey, jiraStatus, jiraIssues, linkedJiraIssues, linkedIssueSignals) {
27148
27174
  const promptParts = [];
27149
27175
  const primaryData = jiraIssues.get(jiraKey);
@@ -27166,7 +27192,7 @@ ${commentTexts}`);
27166
27192
  promptParts.push(`### Linked: ${signal.sourceKey} (${signal.linkType})
27167
27193
  ${commentTexts}`);
27168
27194
  }
27169
- if (promptParts.length === 0) return null;
27195
+ if (promptParts.length === 0) return { summary: null, progressEstimate: null };
27170
27196
  const prompt = promptParts.join("\n\n");
27171
27197
  const result = query3({
27172
27198
  prompt,
@@ -27183,11 +27209,25 @@ ${commentTexts}`);
27183
27209
  (b) => b.type === "text"
27184
27210
  );
27185
27211
  if (textBlock) {
27186
- return textBlock.text.trim();
27212
+ return parseCommentAnalysis(textBlock.text.trim());
27187
27213
  }
27188
27214
  }
27189
27215
  }
27190
- return null;
27216
+ return { summary: null, progressEstimate: null };
27217
+ }
27218
+ function parseCommentAnalysis(text) {
27219
+ const parsed = parseLlmJson(text);
27220
+ if (parsed && typeof parsed.summary === "string") {
27221
+ const progressEstimate2 = typeof parsed.progressEstimate === "number" && parsed.progressEstimate >= 0 && parsed.progressEstimate <= 100 ? Math.round(parsed.progressEstimate) : null;
27222
+ return { summary: parsed.summary, progressEstimate: progressEstimate2 };
27223
+ }
27224
+ let progressEstimate = null;
27225
+ const pctMatch = text.match(/(\d{1,3})%/);
27226
+ if (pctMatch) {
27227
+ const pct = parseInt(pctMatch[1], 10);
27228
+ if (pct >= 0 && pct <= 100) progressEstimate = pct;
27229
+ }
27230
+ return { summary: text, progressEstimate };
27191
27231
  }
27192
27232
  function emptyArtifactReport(artifactId, errors) {
27193
27233
  return {
@@ -27207,6 +27247,7 @@ function emptyArtifactReport(artifactId, errors) {
27207
27247
  progressDrift: false,
27208
27248
  commentSignals: [],
27209
27249
  commentSummary: null,
27250
+ commentAnalysisProgress: null,
27210
27251
  linkedIssues: [],
27211
27252
  linkedIssueSignals: [],
27212
27253
  children: [],
@@ -27244,6 +27285,9 @@ function formatArtifactReport(report) {
27244
27285
  if (report.commentSummary) {
27245
27286
  parts.push(`## Comments`);
27246
27287
  parts.push(report.commentSummary);
27288
+ if (report.commentAnalysisProgress !== null) {
27289
+ parts.push(` \u{1F4CA} Comment-derived progress estimate: ${report.commentAnalysisProgress}%`);
27290
+ }
27247
27291
  parts.push("");
27248
27292
  }
27249
27293
  if (report.children.length > 0) {
@@ -27324,6 +27368,12 @@ function formatArtifactChild(parts, child, depth) {
27324
27368
  formatArtifactChild(parts, grandchild, depth + 1);
27325
27369
  }
27326
27370
  }
27371
+ function findLast(arr, predicate) {
27372
+ for (let i = arr.length - 1; i >= 0; i--) {
27373
+ if (predicate(arr[i])) return arr[i];
27374
+ }
27375
+ return void 0;
27376
+ }
27327
27377
 
27328
27378
  // src/skills/builtin/jira/tools.ts
27329
27379
  var JIRA_TYPE = "jira-issue";
@@ -33567,7 +33617,7 @@ function createProgram() {
33567
33617
  const program2 = new Command();
33568
33618
  program2.name("marvin").description(
33569
33619
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
33570
- ).version("0.5.21");
33620
+ ).version("0.5.23");
33571
33621
  program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
33572
33622
  await initCommand();
33573
33623
  });