mrvn-cli 0.5.15 → 0.5.17

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.d.ts CHANGED
@@ -16,14 +16,39 @@ interface MarvinUserConfig {
16
16
  interface GitConfig {
17
17
  remote?: string;
18
18
  }
19
- interface JiraStatusMap {
19
+ /**
20
+ * Conditional status mapping: Jira status → Marvin status with sprint context.
21
+ * Used when a Jira status should map differently based on sprint membership.
22
+ */
23
+ interface ConditionalJiraStatusMapping {
24
+ default: string;
25
+ inSprint?: string;
26
+ }
27
+ /**
28
+ * Flat Jira→Marvin status map (spec format).
29
+ * Keys are Jira status names, values are Marvin status strings or conditional objects.
30
+ *
31
+ * Example:
32
+ * "In Progress": in-progress
33
+ * "To Do":
34
+ * default: backlog
35
+ * inSprint: ready
36
+ */
37
+ interface FlatJiraStatusMap {
38
+ [jiraStatus: string]: string | ConditionalJiraStatusMapping;
39
+ }
40
+ /**
41
+ * Legacy Marvin→Jira[] status map (nested format).
42
+ * Keys are Marvin status names, values are arrays of Jira status names.
43
+ */
44
+ interface LegacyJiraStatusMap {
20
45
  [marvinStatus: string]: string[];
21
46
  }
22
47
  interface JiraProjectConfig {
23
48
  projectKey?: string;
24
- statusMap?: {
25
- action?: JiraStatusMap;
26
- task?: JiraStatusMap;
49
+ statusMap?: FlatJiraStatusMap | {
50
+ action?: LegacyJiraStatusMap;
51
+ task?: LegacyJiraStatusMap;
27
52
  };
28
53
  }
29
54
  interface MarvinProjectConfig {
package/dist/index.js CHANGED
@@ -14505,11 +14505,25 @@ import { tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
14505
14505
 
14506
14506
  // src/storage/progress.ts
14507
14507
  var DONE_STATUSES = /* @__PURE__ */ new Set(["done", "closed", "resolved", "cancelled"]);
14508
+ var STATUS_PROGRESS_DEFAULTS = {
14509
+ done: 100,
14510
+ closed: 100,
14511
+ resolved: 100,
14512
+ obsolete: 100,
14513
+ "wont do": 100,
14514
+ cancelled: 100,
14515
+ review: 80,
14516
+ "in-progress": 40,
14517
+ ready: 5,
14518
+ blocked: 10,
14519
+ backlog: 0,
14520
+ open: 0
14521
+ };
14508
14522
  function getEffectiveProgress(frontmatter) {
14509
14523
  if (DONE_STATUSES.has(frontmatter.status)) return 100;
14510
14524
  const raw = frontmatter.progress;
14511
14525
  if (typeof raw === "number") return Math.max(0, Math.min(100, Math.round(raw)));
14512
- return 0;
14526
+ return STATUS_PROGRESS_DEFAULTS[frontmatter.status] ?? 0;
14513
14527
  }
14514
14528
  function propagateProgressFromTask(store, taskId) {
14515
14529
  const updated = [];
@@ -25092,24 +25106,80 @@ var DEFAULT_TASK_STATUS_MAP = {
25092
25106
  blocked: ["Blocked"],
25093
25107
  backlog: ["To Do", "Open", "Backlog", "New"]
25094
25108
  };
25095
- function buildStatusLookup(configMap, defaults) {
25096
- const map2 = configMap ?? defaults;
25109
+ function isLegacyFormat(statusMap) {
25110
+ if (!statusMap || typeof statusMap !== "object") return false;
25111
+ const keys = Object.keys(statusMap);
25112
+ if (!keys.every((k) => k === "action" || k === "task")) return false;
25113
+ for (const key of keys) {
25114
+ const val = statusMap[key];
25115
+ if (typeof val !== "object" || val === null) return false;
25116
+ for (const innerVal of Object.values(val)) {
25117
+ if (!Array.isArray(innerVal)) return false;
25118
+ if (!innerVal.every((v) => typeof v === "string")) return false;
25119
+ }
25120
+ }
25121
+ return true;
25122
+ }
25123
+ function buildLegacyLookup(legacyMap) {
25097
25124
  const lookup = /* @__PURE__ */ new Map();
25098
- for (const [marvinStatus, jiraStatuses] of Object.entries(map2)) {
25125
+ for (const [marvinStatus, jiraStatuses] of Object.entries(legacyMap)) {
25099
25126
  for (const js of jiraStatuses) {
25100
25127
  lookup.set(js.toLowerCase(), marvinStatus);
25101
25128
  }
25102
25129
  }
25103
25130
  return lookup;
25104
25131
  }
25105
- function mapJiraStatusForAction(status, configMap) {
25106
- const lookup = buildStatusLookup(configMap, DEFAULT_ACTION_STATUS_MAP);
25132
+ function buildFlatLookup(flatMap, inSprint) {
25133
+ const lookup = /* @__PURE__ */ new Map();
25134
+ for (const [jiraStatus, value] of Object.entries(flatMap)) {
25135
+ if (typeof value === "string") {
25136
+ lookup.set(jiraStatus.toLowerCase(), value);
25137
+ } else {
25138
+ const resolved = inSprint && value.inSprint ? value.inSprint : value.default;
25139
+ lookup.set(jiraStatus.toLowerCase(), resolved);
25140
+ }
25141
+ }
25142
+ return lookup;
25143
+ }
25144
+ function normalizeStatusMap(statusMap) {
25145
+ if (!statusMap) return {};
25146
+ if (isLegacyFormat(statusMap)) {
25147
+ return { legacy: statusMap };
25148
+ }
25149
+ return { flat: statusMap };
25150
+ }
25151
+ function mapJiraStatusForAction(status, resolved, inSprint = false) {
25152
+ if (resolved.flat) {
25153
+ const lookup2 = buildFlatLookup(resolved.flat, inSprint);
25154
+ return lookup2.get(status.toLowerCase()) ?? "open";
25155
+ }
25156
+ const lookup = buildLegacyLookup(resolved.legacy?.action ?? DEFAULT_ACTION_STATUS_MAP);
25107
25157
  return lookup.get(status.toLowerCase()) ?? "open";
25108
25158
  }
25109
- function mapJiraStatusForTask(status, configMap) {
25110
- const lookup = buildStatusLookup(configMap, DEFAULT_TASK_STATUS_MAP);
25159
+ function mapJiraStatusForTask(status, resolved, inSprint = false) {
25160
+ if (resolved.flat) {
25161
+ const lookup2 = buildFlatLookup(resolved.flat, inSprint);
25162
+ return lookup2.get(status.toLowerCase()) ?? "backlog";
25163
+ }
25164
+ const lookup = buildLegacyLookup(resolved.legacy?.task ?? DEFAULT_TASK_STATUS_MAP);
25111
25165
  return lookup.get(status.toLowerCase()) ?? "backlog";
25112
25166
  }
25167
+ function isInActiveSprint(store, tags) {
25168
+ if (!tags) return false;
25169
+ const sprintTags = tags.filter((t) => t.startsWith("sprint:"));
25170
+ if (sprintTags.length === 0) return false;
25171
+ for (const tag of sprintTags) {
25172
+ const sprintId = tag.slice(7);
25173
+ const sprintDoc = store.get(sprintId);
25174
+ if (sprintDoc) {
25175
+ const status = sprintDoc.frontmatter.status;
25176
+ if (status === "active" || status === "completed") {
25177
+ return true;
25178
+ }
25179
+ }
25180
+ }
25181
+ return false;
25182
+ }
25113
25183
  function extractJiraKeyFromTags(tags) {
25114
25184
  if (!tags) return void 0;
25115
25185
  const tag = tags.find((t) => /^jira:[A-Z]+-\d+$/i.test(t));
@@ -25151,7 +25221,9 @@ async function fetchJiraStatus(store, client, host, artifactId, statusMap) {
25151
25221
  const artifactType = doc.frontmatter.type;
25152
25222
  try {
25153
25223
  const issue2 = await client.getIssueWithLinks(jiraKey);
25154
- const proposedStatus = artifactType === "task" ? mapJiraStatusForTask(issue2.fields.status.name, statusMap?.task) : mapJiraStatusForAction(issue2.fields.status.name, statusMap?.action);
25224
+ const inSprint = isInActiveSprint(store, doc.frontmatter.tags);
25225
+ const resolved = statusMap ?? {};
25226
+ const proposedStatus = artifactType === "task" ? mapJiraStatusForTask(issue2.fields.status.name, resolved, inSprint) : mapJiraStatusForAction(issue2.fields.status.name, resolved, inSprint);
25155
25227
  const currentStatus = doc.frontmatter.status;
25156
25228
  const linkedIssues = [];
25157
25229
  if (issue2.fields.subtasks) {
@@ -25503,7 +25575,7 @@ async function fetchJiraDaily(store, client, host, projectKey, dateRange, status
25503
25575
  const batch = issues.slice(i, i + BATCH_SIZE2);
25504
25576
  const results = await Promise.allSettled(
25505
25577
  batch.map(
25506
- (issue2) => processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap)
25578
+ (issue2) => processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap, store)
25507
25579
  )
25508
25580
  );
25509
25581
  for (let j = 0; j < results.length; j++) {
@@ -25520,7 +25592,7 @@ async function fetchJiraDaily(store, client, host, projectKey, dateRange, status
25520
25592
  summary.proposedActions = generateProposedActions(summary.issues);
25521
25593
  return summary;
25522
25594
  }
25523
- async function processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap) {
25595
+ async function processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap, store) {
25524
25596
  const [changelogResult, commentsResult, remoteLinksResult, issueWithLinks] = await Promise.all([
25525
25597
  client.getChangelog(issue2.key).catch(() => []),
25526
25598
  client.getComments(issue2.key).catch(() => []),
@@ -25608,7 +25680,9 @@ async function processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts,
25608
25680
  if (artifactType === "action" || artifactType === "task") {
25609
25681
  const jiraStatus = issue2.fields.status?.name;
25610
25682
  if (jiraStatus) {
25611
- proposedStatus = artifactType === "task" ? mapJiraStatusForTask(jiraStatus, statusMap?.task) : mapJiraStatusForAction(jiraStatus, statusMap?.action);
25683
+ const inSprint = store ? isInActiveSprint(store, fm.tags) : false;
25684
+ const resolved = statusMap ?? {};
25685
+ proposedStatus = artifactType === "task" ? mapJiraStatusForTask(jiraStatus, resolved, inSprint) : mapJiraStatusForAction(jiraStatus, resolved, inSprint);
25612
25686
  }
25613
25687
  }
25614
25688
  marvinArtifacts.push({
@@ -25737,20 +25811,6 @@ var COMPLEXITY_WEIGHTS = {
25737
25811
  "very-complex": 8
25738
25812
  };
25739
25813
  var DEFAULT_WEIGHT = 3;
25740
- var STATUS_PROGRESS_DEFAULTS = {
25741
- done: 100,
25742
- closed: 100,
25743
- resolved: 100,
25744
- obsolete: 100,
25745
- "wont do": 100,
25746
- cancelled: 100,
25747
- review: 80,
25748
- "in-progress": 40,
25749
- ready: 5,
25750
- backlog: 0,
25751
- open: 0
25752
- };
25753
- var BLOCKED_DEFAULT_PROGRESS = 10;
25754
25814
  function resolveWeight(complexity) {
25755
25815
  if (complexity && complexity in COMPLEXITY_WEIGHTS) {
25756
25816
  return { weight: COMPLEXITY_WEIGHTS[complexity], weightSource: "complexity" };
@@ -25766,9 +25826,6 @@ function resolveProgress(frontmatter, commentAnalysisProgress) {
25766
25826
  return { progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))), progressSource: "comment-analysis" };
25767
25827
  }
25768
25828
  const status = frontmatter.status;
25769
- if (status === "blocked") {
25770
- return { progress: BLOCKED_DEFAULT_PROGRESS, progressSource: "status-default" };
25771
- }
25772
25829
  const defaultProgress = STATUS_PROGRESS_DEFAULTS[status] ?? 0;
25773
25830
  return { progress: defaultProgress, progressSource: "status-default" };
25774
25831
  }
@@ -25860,7 +25917,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
25860
25917
  const commentSignals = [];
25861
25918
  if (jiraData) {
25862
25919
  jiraStatus = jiraData.issue.fields.status.name;
25863
- proposedMarvinStatus = fm.type === "task" ? mapJiraStatusForTask(jiraStatus, options.statusMap?.task) : mapJiraStatusForAction(jiraStatus, options.statusMap?.action);
25920
+ const inSprint = isInActiveSprint(store, fm.tags);
25921
+ const resolved = options.statusMap ?? {};
25922
+ proposedMarvinStatus = fm.type === "task" ? mapJiraStatusForTask(jiraStatus, resolved, inSprint) : mapJiraStatusForAction(jiraStatus, resolved, inSprint);
25864
25923
  const subtasks = jiraData.issue.fields.subtasks ?? [];
25865
25924
  if (subtasks.length > 0) {
25866
25925
  jiraSubtaskProgress = computeSubtaskProgress(subtasks);
@@ -25891,6 +25950,20 @@ async function assessSprintProgress(store, client, host, options = {}) {
25891
25950
  proposedValue: jiraSubtaskProgress,
25892
25951
  reason: `Jira ${jiraKey} subtask progress is ${jiraSubtaskProgress}%`
25893
25952
  });
25953
+ } else if (statusDrift && proposedMarvinStatus && !fm.progressOverride) {
25954
+ const hasExplicitProgress = "progress" in fm && typeof fm.progress === "number";
25955
+ if (!hasExplicitProgress) {
25956
+ const proposedProgress = STATUS_PROGRESS_DEFAULTS[proposedMarvinStatus] ?? 0;
25957
+ if (proposedProgress !== currentProgress) {
25958
+ proposedUpdates.push({
25959
+ artifactId: fm.id,
25960
+ field: "progress",
25961
+ currentValue: currentProgress,
25962
+ proposedValue: proposedProgress,
25963
+ reason: `Status changing to "${proposedMarvinStatus}" \u2192 default progress ${proposedProgress}%`
25964
+ });
25965
+ }
25966
+ }
25894
25967
  }
25895
25968
  const tags = fm.tags ?? [];
25896
25969
  const focusTag = tags.find((t) => t.startsWith("focus:"));
@@ -26269,7 +26342,7 @@ function findByJiraKey(store, jiraKey) {
26269
26342
  function createJiraTools(store, projectConfig) {
26270
26343
  const jiraUserConfig = loadUserConfig().jira;
26271
26344
  const defaultProjectKey = projectConfig?.jira?.projectKey;
26272
- const statusMap = projectConfig?.jira?.statusMap;
26345
+ const statusMap = normalizeStatusMap(projectConfig?.jira?.statusMap);
26273
26346
  return [
26274
26347
  // --- Local read tools ---
26275
26348
  tool20(
@@ -26896,15 +26969,32 @@ function createJiraTools(store, projectConfig) {
26896
26969
  const s = issue2.fields.status.name;
26897
26970
  statusCounts.set(s, (statusCounts.get(s) ?? 0) + 1);
26898
26971
  }
26899
- const actionMap = statusMap?.action ?? DEFAULT_ACTION_STATUS_MAP;
26900
- const taskMap = statusMap?.task ?? DEFAULT_TASK_STATUS_MAP;
26901
26972
  const actionLookup = /* @__PURE__ */ new Map();
26902
- for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
26903
- for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
26904
- }
26905
26973
  const taskLookup = /* @__PURE__ */ new Map();
26906
- for (const [marvin, jiraStatuses] of Object.entries(taskMap)) {
26907
- for (const js of jiraStatuses) taskLookup.set(js.toLowerCase(), marvin);
26974
+ if (statusMap.flat) {
26975
+ for (const [jiraStatus, value] of Object.entries(statusMap.flat)) {
26976
+ const lower = jiraStatus.toLowerCase();
26977
+ if (typeof value === "string") {
26978
+ actionLookup.set(lower, value);
26979
+ taskLookup.set(lower, value);
26980
+ } else {
26981
+ actionLookup.set(lower, value.default);
26982
+ taskLookup.set(lower, value.default);
26983
+ if (value.inSprint) {
26984
+ actionLookup.set(lower, `${value.default} / ${value.inSprint} (inSprint)`);
26985
+ taskLookup.set(lower, `${value.default} / ${value.inSprint} (inSprint)`);
26986
+ }
26987
+ }
26988
+ }
26989
+ } else {
26990
+ const actionMap = statusMap.legacy?.action ?? DEFAULT_ACTION_STATUS_MAP;
26991
+ const taskMap = statusMap.legacy?.task ?? DEFAULT_TASK_STATUS_MAP;
26992
+ for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
26993
+ for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
26994
+ }
26995
+ for (const [marvin, jiraStatuses] of Object.entries(taskMap)) {
26996
+ for (const js of jiraStatuses) taskLookup.set(js.toLowerCase(), marvin);
26997
+ }
26908
26998
  }
26909
26999
  const parts = [
26910
27000
  `Found ${statusCounts.size} distinct statuses in ${resolvedProjectKey} (scanned ${data.issues.length} of ${data.total} issues):`,
@@ -26925,25 +27015,20 @@ function createJiraTools(store, projectConfig) {
26925
27015
  if (!taskTarget) unmappedTask.push(status);
26926
27016
  }
26927
27017
  if (unmappedAction.length > 0 || unmappedTask.length > 0) {
27018
+ const allUnmapped = [.../* @__PURE__ */ new Set([...unmappedAction, ...unmappedTask])];
26928
27019
  parts.push("");
26929
27020
  parts.push("To fix unmapped statuses, add jira.statusMap to .marvin/config.yaml:");
26930
27021
  parts.push(" jira:");
26931
27022
  parts.push(" statusMap:");
26932
- if (unmappedAction.length > 0) {
26933
- parts.push(" action:");
26934
- parts.push(` # Map these: ${unmappedAction.join(", ")}`);
26935
- parts.push(" # <marvin-status>: [<jira-status>, ...]");
26936
- }
26937
- if (unmappedTask.length > 0) {
26938
- parts.push(" task:");
26939
- parts.push(` # Map these: ${unmappedTask.join(", ")}`);
26940
- parts.push(" # <marvin-status>: [<jira-status>, ...]");
27023
+ for (const s of allUnmapped) {
27024
+ parts.push(` "${s}": <marvin-status>`);
26941
27025
  }
27026
+ parts.push(" # Supported marvin statuses: done, in-progress, review, ready, blocked, backlog, open");
26942
27027
  } else {
26943
27028
  parts.push("");
26944
27029
  parts.push("All statuses are mapped.");
26945
27030
  }
26946
- const usingConfig = statusMap?.action || statusMap?.task;
27031
+ const usingConfig = statusMap.flat || statusMap.legacy;
26947
27032
  parts.push("");
26948
27033
  parts.push(usingConfig ? "Using status maps from .marvin/config.yaml." : "Using built-in default status maps (no jira.statusMap in config).");
26949
27034
  return {
@@ -32332,7 +32417,7 @@ async function jiraSyncCommand(artifactId, options = {}) {
32332
32417
  );
32333
32418
  return;
32334
32419
  }
32335
- const statusMap = project.config.jira?.statusMap;
32420
+ const statusMap = normalizeStatusMap(project.config.jira?.statusMap);
32336
32421
  const label = artifactId ? `Checking ${artifactId} against Jira...` : "Checking all Jira-linked actions/tasks...";
32337
32422
  console.log(chalk20.dim(label));
32338
32423
  if (options.dryRun) {
@@ -32449,9 +32534,7 @@ async function jiraStatusesCommand(projectKey) {
32449
32534
  return;
32450
32535
  }
32451
32536
  console.log(chalk20.dim(`Fetching statuses from Jira project ${resolvedProjectKey}...`));
32452
- const statusMap = project.config.jira?.statusMap;
32453
- const actionMap = statusMap?.action ?? DEFAULT_ACTION_STATUS_MAP;
32454
- const taskMap = statusMap?.task ?? DEFAULT_TASK_STATUS_MAP;
32537
+ const statusMap = normalizeStatusMap(project.config.jira?.statusMap);
32455
32538
  const email3 = jiraUserConfig?.email ?? process.env.JIRA_EMAIL;
32456
32539
  const apiToken = jiraUserConfig?.apiToken ?? process.env.JIRA_API_TOKEN;
32457
32540
  const auth = "Basic " + Buffer.from(`${email3}:${apiToken}`).toString("base64");
@@ -32475,12 +32558,28 @@ async function jiraStatusesCommand(projectKey) {
32475
32558
  statusCounts.set(s, (statusCounts.get(s) ?? 0) + 1);
32476
32559
  }
32477
32560
  const actionLookup = /* @__PURE__ */ new Map();
32478
- for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
32479
- for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
32480
- }
32481
32561
  const taskLookup = /* @__PURE__ */ new Map();
32482
- for (const [marvin, jiraStatuses] of Object.entries(taskMap)) {
32483
- for (const js of jiraStatuses) taskLookup.set(js.toLowerCase(), marvin);
32562
+ if (statusMap.flat) {
32563
+ for (const [jiraStatus, value] of Object.entries(statusMap.flat)) {
32564
+ const lower = jiraStatus.toLowerCase();
32565
+ if (typeof value === "string") {
32566
+ actionLookup.set(lower, value);
32567
+ taskLookup.set(lower, value);
32568
+ } else {
32569
+ const label = value.inSprint ? `${value.default} / ${value.inSprint} (inSprint)` : value.default;
32570
+ actionLookup.set(lower, label);
32571
+ taskLookup.set(lower, label);
32572
+ }
32573
+ }
32574
+ } else {
32575
+ const actionMap = statusMap.legacy?.action ?? DEFAULT_ACTION_STATUS_MAP;
32576
+ const taskMap = statusMap.legacy?.task ?? DEFAULT_TASK_STATUS_MAP;
32577
+ for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
32578
+ for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
32579
+ }
32580
+ for (const [marvin, jiraStatuses] of Object.entries(taskMap)) {
32581
+ for (const js of jiraStatuses) taskLookup.set(js.toLowerCase(), marvin);
32582
+ }
32484
32583
  }
32485
32584
  console.log(
32486
32585
  `
@@ -32503,14 +32602,11 @@ Found ${chalk20.bold(String(statusCounts.size))} distinct statuses in ${chalk20.
32503
32602
  console.log(chalk20.yellow("\nSome statuses are unmapped. Add jira.statusMap to .marvin/config.yaml:"));
32504
32603
  console.log(chalk20.dim(" jira:"));
32505
32604
  console.log(chalk20.dim(" statusMap:"));
32506
- console.log(chalk20.dim(" action:"));
32507
- console.log(chalk20.dim(' <marvin-status>: ["<Jira Status>", ...]'));
32508
- console.log(chalk20.dim(" task:"));
32509
- console.log(chalk20.dim(' <marvin-status>: ["<Jira Status>", ...]'));
32605
+ console.log(chalk20.dim(' "<Jira Status>": <marvin-status>'));
32510
32606
  } else {
32511
32607
  console.log(chalk20.green("\nAll statuses are mapped."));
32512
32608
  }
32513
- const usingConfig = statusMap?.action || statusMap?.task;
32609
+ const usingConfig = statusMap.flat || statusMap.legacy;
32514
32610
  console.log(
32515
32611
  chalk20.dim(
32516
32612
  usingConfig ? "\nUsing status maps from .marvin/config.yaml." : "\nUsing built-in default status maps (no jira.statusMap in config)."
@@ -32545,7 +32641,7 @@ async function jiraDailyCommand(options) {
32545
32641
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
32546
32642
  const fromDate = options.from ?? today;
32547
32643
  const toDate = options.to ?? fromDate;
32548
- const statusMap = proj.config.jira?.statusMap;
32644
+ const statusMap = normalizeStatusMap(proj.config.jira?.statusMap);
32549
32645
  const rangeLabel = fromDate === toDate ? fromDate : `${fromDate} to ${toDate}`;
32550
32646
  console.log(
32551
32647
  chalk20.dim(`Fetching Jira daily summary for ${resolvedProjectKey} \u2014 ${rangeLabel}...`)
@@ -32662,7 +32758,7 @@ function createProgram() {
32662
32758
  const program = new Command();
32663
32759
  program.name("marvin").description(
32664
32760
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
32665
- ).version("0.5.15");
32761
+ ).version("0.5.17");
32666
32762
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
32667
32763
  await initCommand();
32668
32764
  });