mrvn-cli 0.5.19 → 0.5.21

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 CHANGED
@@ -14525,24 +14525,26 @@ function getEffectiveProgress(frontmatter) {
14525
14525
  if (typeof raw === "number") return Math.max(0, Math.min(100, Math.round(raw)));
14526
14526
  return STATUS_PROGRESS_DEFAULTS[frontmatter.status] ?? 0;
14527
14527
  }
14528
- function propagateProgressFromTask(store, taskId) {
14528
+ function propagateProgressFromTask(store, taskId, options) {
14529
14529
  const updated = [];
14530
14530
  const task = store.get(taskId);
14531
14531
  if (!task) return updated;
14532
- if (DONE_STATUSES.has(task.frontmatter.status)) {
14533
- if (task.frontmatter.progress !== 100) {
14534
- store.update(taskId, { progress: 100 });
14535
- updated.push(taskId);
14536
- }
14537
- } else if (!task.frontmatter.progressOverride) {
14538
- const children = store.list({ type: "contribution" }).filter((d) => d.frontmatter.aboutArtifact === taskId);
14539
- if (children.length > 0) {
14540
- const avg = children.reduce((sum, c) => sum + getEffectiveProgress(c.frontmatter), 0) / children.length;
14541
- const progress = Math.round(avg);
14542
- if (task.frontmatter.progress !== progress) {
14543
- store.update(taskId, { progress });
14532
+ if (!options?.skipSelf) {
14533
+ if (DONE_STATUSES.has(task.frontmatter.status)) {
14534
+ if (task.frontmatter.progress !== 100) {
14535
+ store.update(taskId, { progress: 100 });
14544
14536
  updated.push(taskId);
14545
14537
  }
14538
+ } else if (!task.frontmatter.progressOverride) {
14539
+ const children = store.list({ type: "contribution" }).filter((d) => d.frontmatter.aboutArtifact === taskId);
14540
+ if (children.length > 0) {
14541
+ const avg = children.reduce((sum, c) => sum + getEffectiveProgress(c.frontmatter), 0) / children.length;
14542
+ const progress = Math.round(avg);
14543
+ if (task.frontmatter.progress !== progress) {
14544
+ store.update(taskId, { progress });
14545
+ updated.push(taskId);
14546
+ }
14547
+ }
14546
14548
  }
14547
14549
  }
14548
14550
  const aboutArtifact = task.frontmatter.aboutArtifact;
@@ -14554,10 +14556,11 @@ function propagateProgressFromTask(store, taskId) {
14554
14556
  }
14555
14557
  return updated;
14556
14558
  }
14557
- function propagateProgressToAction(store, actionId) {
14559
+ function propagateProgressToAction(store, actionId, options) {
14558
14560
  const updated = [];
14559
14561
  const action = store.get(actionId);
14560
14562
  if (!action) return updated;
14563
+ if (options?.skipSelf) return updated;
14561
14564
  if (DONE_STATUSES.has(action.frontmatter.status)) {
14562
14565
  if (action.frontmatter.progress !== 100) {
14563
14566
  store.update(actionId, { progress: 100 });
@@ -14742,10 +14745,11 @@ function createActionTools(store) {
14742
14745
  tags: external_exports.array(external_exports.string()).optional().describe("Replace all tags. When provided with sprints, sprint tags are merged into this array."),
14743
14746
  sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001']."),
14744
14747
  workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Replaces existing focus:<value> tag."),
14745
- progress: external_exports.number().nullable().optional().describe("Explicit progress percentage (0-100). Pass null to clear the override and revert to auto-calculation from children.")
14748
+ progress: external_exports.number().nullable().optional().describe("Explicit progress percentage (0-100). Pass null to clear the override and revert to auto-calculation from children."),
14749
+ progressOverride: external_exports.boolean().optional().describe("Control auto-calculation lock. true = lock progress to explicit value, false = allow auto-calculation from children. When omitted: setting progress implies true, null progress implies false.")
14746
14750
  },
14747
14751
  async (args) => {
14748
- const { id, content, sprints, tags, workFocus, progress, owner, assignee, ...updates } = args;
14752
+ const { id, content, sprints, tags, workFocus, progress, progressOverride, owner, assignee, ...updates } = args;
14749
14753
  if (owner !== void 0) updates.owner = normalizeOwner(owner);
14750
14754
  if (assignee !== void 0) updates.assignee = assignee;
14751
14755
  if (tags !== void 0) {
@@ -14784,13 +14788,17 @@ function createActionTools(store) {
14784
14788
  }
14785
14789
  if (typeof progress === "number") {
14786
14790
  updates.progress = Math.max(0, Math.min(100, Math.round(progress)));
14787
- updates.progressOverride = true;
14791
+ updates.progressOverride = progressOverride ?? true;
14788
14792
  } else if (progress === null) {
14789
14793
  updates.progressOverride = false;
14794
+ } else if (progressOverride !== void 0) {
14795
+ updates.progressOverride = progressOverride;
14790
14796
  }
14791
14797
  const doc = store.update(id, updates, content);
14792
14798
  if (args.status !== void 0 || typeof progress === "number") {
14793
- propagateProgressToAction(store, id);
14799
+ propagateProgressToAction(store, id, {
14800
+ skipSelf: typeof progress === "number"
14801
+ });
14794
14802
  }
14795
14803
  return {
14796
14804
  content: [
@@ -23792,10 +23800,11 @@ function createTaskTools(store) {
23792
23800
  priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
23793
23801
  tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)"),
23794
23802
  workFocus: external_exports.string().optional().describe("Work focus name (e.g. 'Budget UX'). Replaces existing focus:<value> tag."),
23795
- progress: external_exports.number().nullable().optional().describe("Explicit progress percentage (0-100). Overrides auto-calculation from child contributions. Pass null to clear the override and revert to auto-calculation.")
23803
+ progress: external_exports.number().nullable().optional().describe("Explicit progress percentage (0-100). Overrides auto-calculation from child contributions. Pass null to clear the override and revert to auto-calculation."),
23804
+ progressOverride: external_exports.boolean().optional().describe("Control auto-calculation lock. true = lock progress to explicit value, false = allow auto-calculation from children. When omitted: setting progress implies true, null progress implies false.")
23796
23805
  },
23797
23806
  async (args) => {
23798
- const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, workFocus, progress, ...updates } = args;
23807
+ const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, workFocus, progress, progressOverride, ...updates } = args;
23799
23808
  const warnings = [];
23800
23809
  if (rawLinkedEpic !== void 0) {
23801
23810
  const linkedEpics = normalizeLinkedEpics(rawLinkedEpic);
@@ -23824,13 +23833,17 @@ function createTaskTools(store) {
23824
23833
  }
23825
23834
  if (typeof progress === "number") {
23826
23835
  updates.progress = Math.max(0, Math.min(100, Math.round(progress)));
23827
- updates.progressOverride = true;
23836
+ updates.progressOverride = progressOverride ?? true;
23828
23837
  } else if (progress === null) {
23829
23838
  updates.progressOverride = false;
23839
+ } else if (progressOverride !== void 0) {
23840
+ updates.progressOverride = progressOverride;
23830
23841
  }
23831
23842
  const doc = store.update(id, updates, content);
23832
23843
  if (args.status !== void 0 || typeof progress === "number") {
23833
- propagateProgressFromTask(store, id);
23844
+ propagateProgressFromTask(store, id, {
23845
+ skipSelf: typeof progress === "number"
23846
+ });
23834
23847
  }
23835
23848
  const parts = [`Updated task ${doc.frontmatter.id}: ${doc.frontmatter.title}`];
23836
23849
  if (warnings.length > 0) {
@@ -26720,11 +26733,8 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
26720
26733
  children.push(childReport);
26721
26734
  }
26722
26735
  if (children.length > 0) {
26723
- const rolledUpProgress = computeWeightedProgress(
26724
- children.map((c) => ({
26725
- weight: resolveWeight(void 0).weight,
26726
- progress: c.marvinProgress
26727
- }))
26736
+ const rolledUpProgress = Math.round(
26737
+ children.reduce((s, c) => s + c.marvinProgress, 0) / children.length
26728
26738
  );
26729
26739
  if (rolledUpProgress !== currentProgress) {
26730
26740
  proposedUpdates.push({
@@ -26732,15 +26742,21 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
26732
26742
  field: "progress",
26733
26743
  currentValue: currentProgress,
26734
26744
  proposedValue: rolledUpProgress,
26735
- reason: `Rolled up from ${children.length} children (weighted average ${rolledUpProgress}%)`
26745
+ reason: `Rolled up from ${children.length} children (average ${rolledUpProgress}%)`
26736
26746
  });
26737
26747
  }
26738
26748
  }
26739
26749
  const signals = buildSignals(commentSignals, linkedIssues, statusDrift, proposedMarvinStatus);
26740
26750
  const appliedUpdates = [];
26741
26751
  if (options.applyUpdates && proposedUpdates.length > 0) {
26752
+ const doneArtifacts = new Set(
26753
+ proposedUpdates.filter((u) => u.field === "status" && DONE_STATUSES15.has(String(u.proposedValue))).map((u) => u.artifactId)
26754
+ );
26742
26755
  for (const update of proposedUpdates) {
26743
26756
  if (update.field === "review") continue;
26757
+ if (update.field === "progress" && doneArtifacts.has(update.artifactId)) {
26758
+ if (update.proposedValue !== 100) continue;
26759
+ }
26744
26760
  try {
26745
26761
  const updatePayload = {
26746
26762
  [update.field]: update.proposedValue,
@@ -26750,12 +26766,14 @@ async function _assessArtifactRecursive(store, client, host, options, visited, d
26750
26766
  updatePayload.progressOverride = false;
26751
26767
  }
26752
26768
  store.update(update.artifactId, updatePayload);
26753
- const updatedDoc = store.get(update.artifactId);
26754
- if (updatedDoc) {
26755
- if (updatedDoc.frontmatter.type === "task") {
26756
- propagateProgressFromTask(store, update.artifactId);
26757
- } else if (updatedDoc.frontmatter.type === "action") {
26758
- propagateProgressToAction(store, update.artifactId);
26769
+ if (update.field === "status") {
26770
+ const updatedDoc = store.get(update.artifactId);
26771
+ if (updatedDoc) {
26772
+ if (updatedDoc.frontmatter.type === "task") {
26773
+ propagateProgressFromTask(store, update.artifactId);
26774
+ } else if (updatedDoc.frontmatter.type === "action") {
26775
+ propagateProgressToAction(store, update.artifactId);
26776
+ }
26759
26777
  }
26760
26778
  }
26761
26779
  appliedUpdates.push(update);
@@ -26984,11 +27002,9 @@ function formatArtifactReport(report) {
26984
27002
  }
26985
27003
  if (report.children.length > 0) {
26986
27004
  const doneCount = report.children.filter((c) => DONE_STATUSES15.has(c.marvinStatus)).length;
26987
- const childWeights = report.children.map((c) => {
26988
- const { weight } = resolveWeight(void 0);
26989
- return { weight, progress: c.marvinProgress };
26990
- });
26991
- const childProgress = childWeights.length > 0 ? Math.round(childWeights.reduce((s, c) => s + c.weight * c.progress, 0) / childWeights.reduce((s, c) => s + c.weight, 0)) : 0;
27005
+ const childProgress = Math.round(
27006
+ report.children.reduce((s, c) => s + c.marvinProgress, 0) / report.children.length
27007
+ );
26992
27008
  const bar = progressBar6(childProgress);
26993
27009
  parts.push(`## Children (${doneCount}/${report.children.length} done) ${bar} ${childProgress}%`);
26994
27010
  for (const child of report.children) {
@@ -33559,7 +33575,7 @@ function createProgram() {
33559
33575
  const program = new Command();
33560
33576
  program.name("marvin").description(
33561
33577
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
33562
- ).version("0.5.19");
33578
+ ).version("0.5.21");
33563
33579
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
33564
33580
  await initCommand();
33565
33581
  });