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 +29 -4
- package/dist/index.js +160 -64
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +133 -48
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +160 -64
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin.js
CHANGED
|
@@ -14497,11 +14497,25 @@ function evaluateHealth(projectName, metrics) {
|
|
|
14497
14497
|
|
|
14498
14498
|
// src/storage/progress.ts
|
|
14499
14499
|
var DONE_STATUSES = /* @__PURE__ */ new Set(["done", "closed", "resolved", "cancelled"]);
|
|
14500
|
+
var STATUS_PROGRESS_DEFAULTS = {
|
|
14501
|
+
done: 100,
|
|
14502
|
+
closed: 100,
|
|
14503
|
+
resolved: 100,
|
|
14504
|
+
obsolete: 100,
|
|
14505
|
+
"wont do": 100,
|
|
14506
|
+
cancelled: 100,
|
|
14507
|
+
review: 80,
|
|
14508
|
+
"in-progress": 40,
|
|
14509
|
+
ready: 5,
|
|
14510
|
+
blocked: 10,
|
|
14511
|
+
backlog: 0,
|
|
14512
|
+
open: 0
|
|
14513
|
+
};
|
|
14500
14514
|
function getEffectiveProgress(frontmatter) {
|
|
14501
14515
|
if (DONE_STATUSES.has(frontmatter.status)) return 100;
|
|
14502
14516
|
const raw = frontmatter.progress;
|
|
14503
14517
|
if (typeof raw === "number") return Math.max(0, Math.min(100, Math.round(raw)));
|
|
14504
|
-
return 0;
|
|
14518
|
+
return STATUS_PROGRESS_DEFAULTS[frontmatter.status] ?? 0;
|
|
14505
14519
|
}
|
|
14506
14520
|
function propagateProgressFromTask(store, taskId) {
|
|
14507
14521
|
const updated = [];
|
|
@@ -25338,24 +25352,80 @@ var DEFAULT_TASK_STATUS_MAP = {
|
|
|
25338
25352
|
blocked: ["Blocked"],
|
|
25339
25353
|
backlog: ["To Do", "Open", "Backlog", "New"]
|
|
25340
25354
|
};
|
|
25341
|
-
function
|
|
25342
|
-
|
|
25355
|
+
function isLegacyFormat(statusMap) {
|
|
25356
|
+
if (!statusMap || typeof statusMap !== "object") return false;
|
|
25357
|
+
const keys = Object.keys(statusMap);
|
|
25358
|
+
if (!keys.every((k) => k === "action" || k === "task")) return false;
|
|
25359
|
+
for (const key of keys) {
|
|
25360
|
+
const val = statusMap[key];
|
|
25361
|
+
if (typeof val !== "object" || val === null) return false;
|
|
25362
|
+
for (const innerVal of Object.values(val)) {
|
|
25363
|
+
if (!Array.isArray(innerVal)) return false;
|
|
25364
|
+
if (!innerVal.every((v) => typeof v === "string")) return false;
|
|
25365
|
+
}
|
|
25366
|
+
}
|
|
25367
|
+
return true;
|
|
25368
|
+
}
|
|
25369
|
+
function buildLegacyLookup(legacyMap) {
|
|
25343
25370
|
const lookup = /* @__PURE__ */ new Map();
|
|
25344
|
-
for (const [marvinStatus, jiraStatuses] of Object.entries(
|
|
25371
|
+
for (const [marvinStatus, jiraStatuses] of Object.entries(legacyMap)) {
|
|
25345
25372
|
for (const js of jiraStatuses) {
|
|
25346
25373
|
lookup.set(js.toLowerCase(), marvinStatus);
|
|
25347
25374
|
}
|
|
25348
25375
|
}
|
|
25349
25376
|
return lookup;
|
|
25350
25377
|
}
|
|
25351
|
-
function
|
|
25352
|
-
const lookup =
|
|
25378
|
+
function buildFlatLookup(flatMap, inSprint) {
|
|
25379
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
25380
|
+
for (const [jiraStatus, value] of Object.entries(flatMap)) {
|
|
25381
|
+
if (typeof value === "string") {
|
|
25382
|
+
lookup.set(jiraStatus.toLowerCase(), value);
|
|
25383
|
+
} else {
|
|
25384
|
+
const resolved = inSprint && value.inSprint ? value.inSprint : value.default;
|
|
25385
|
+
lookup.set(jiraStatus.toLowerCase(), resolved);
|
|
25386
|
+
}
|
|
25387
|
+
}
|
|
25388
|
+
return lookup;
|
|
25389
|
+
}
|
|
25390
|
+
function normalizeStatusMap(statusMap) {
|
|
25391
|
+
if (!statusMap) return {};
|
|
25392
|
+
if (isLegacyFormat(statusMap)) {
|
|
25393
|
+
return { legacy: statusMap };
|
|
25394
|
+
}
|
|
25395
|
+
return { flat: statusMap };
|
|
25396
|
+
}
|
|
25397
|
+
function mapJiraStatusForAction(status, resolved, inSprint = false) {
|
|
25398
|
+
if (resolved.flat) {
|
|
25399
|
+
const lookup2 = buildFlatLookup(resolved.flat, inSprint);
|
|
25400
|
+
return lookup2.get(status.toLowerCase()) ?? "open";
|
|
25401
|
+
}
|
|
25402
|
+
const lookup = buildLegacyLookup(resolved.legacy?.action ?? DEFAULT_ACTION_STATUS_MAP);
|
|
25353
25403
|
return lookup.get(status.toLowerCase()) ?? "open";
|
|
25354
25404
|
}
|
|
25355
|
-
function mapJiraStatusForTask(status,
|
|
25356
|
-
|
|
25405
|
+
function mapJiraStatusForTask(status, resolved, inSprint = false) {
|
|
25406
|
+
if (resolved.flat) {
|
|
25407
|
+
const lookup2 = buildFlatLookup(resolved.flat, inSprint);
|
|
25408
|
+
return lookup2.get(status.toLowerCase()) ?? "backlog";
|
|
25409
|
+
}
|
|
25410
|
+
const lookup = buildLegacyLookup(resolved.legacy?.task ?? DEFAULT_TASK_STATUS_MAP);
|
|
25357
25411
|
return lookup.get(status.toLowerCase()) ?? "backlog";
|
|
25358
25412
|
}
|
|
25413
|
+
function isInActiveSprint(store, tags) {
|
|
25414
|
+
if (!tags) return false;
|
|
25415
|
+
const sprintTags = tags.filter((t) => t.startsWith("sprint:"));
|
|
25416
|
+
if (sprintTags.length === 0) return false;
|
|
25417
|
+
for (const tag of sprintTags) {
|
|
25418
|
+
const sprintId = tag.slice(7);
|
|
25419
|
+
const sprintDoc = store.get(sprintId);
|
|
25420
|
+
if (sprintDoc) {
|
|
25421
|
+
const status = sprintDoc.frontmatter.status;
|
|
25422
|
+
if (status === "active" || status === "completed") {
|
|
25423
|
+
return true;
|
|
25424
|
+
}
|
|
25425
|
+
}
|
|
25426
|
+
}
|
|
25427
|
+
return false;
|
|
25428
|
+
}
|
|
25359
25429
|
function extractJiraKeyFromTags(tags) {
|
|
25360
25430
|
if (!tags) return void 0;
|
|
25361
25431
|
const tag = tags.find((t) => /^jira:[A-Z]+-\d+$/i.test(t));
|
|
@@ -25397,7 +25467,9 @@ async function fetchJiraStatus(store, client, host, artifactId, statusMap) {
|
|
|
25397
25467
|
const artifactType = doc.frontmatter.type;
|
|
25398
25468
|
try {
|
|
25399
25469
|
const issue2 = await client.getIssueWithLinks(jiraKey);
|
|
25400
|
-
const
|
|
25470
|
+
const inSprint = isInActiveSprint(store, doc.frontmatter.tags);
|
|
25471
|
+
const resolved = statusMap ?? {};
|
|
25472
|
+
const proposedStatus = artifactType === "task" ? mapJiraStatusForTask(issue2.fields.status.name, resolved, inSprint) : mapJiraStatusForAction(issue2.fields.status.name, resolved, inSprint);
|
|
25401
25473
|
const currentStatus = doc.frontmatter.status;
|
|
25402
25474
|
const linkedIssues = [];
|
|
25403
25475
|
if (issue2.fields.subtasks) {
|
|
@@ -25749,7 +25821,7 @@ async function fetchJiraDaily(store, client, host, projectKey, dateRange, status
|
|
|
25749
25821
|
const batch = issues.slice(i, i + BATCH_SIZE2);
|
|
25750
25822
|
const results = await Promise.allSettled(
|
|
25751
25823
|
batch.map(
|
|
25752
|
-
(issue2) => processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap)
|
|
25824
|
+
(issue2) => processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap, store)
|
|
25753
25825
|
)
|
|
25754
25826
|
);
|
|
25755
25827
|
for (let j = 0; j < results.length; j++) {
|
|
@@ -25766,7 +25838,7 @@ async function fetchJiraDaily(store, client, host, projectKey, dateRange, status
|
|
|
25766
25838
|
summary.proposedActions = generateProposedActions(summary.issues);
|
|
25767
25839
|
return summary;
|
|
25768
25840
|
}
|
|
25769
|
-
async function processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap) {
|
|
25841
|
+
async function processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap, store) {
|
|
25770
25842
|
const [changelogResult, commentsResult, remoteLinksResult, issueWithLinks] = await Promise.all([
|
|
25771
25843
|
client.getChangelog(issue2.key).catch(() => []),
|
|
25772
25844
|
client.getComments(issue2.key).catch(() => []),
|
|
@@ -25854,7 +25926,9 @@ async function processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts,
|
|
|
25854
25926
|
if (artifactType === "action" || artifactType === "task") {
|
|
25855
25927
|
const jiraStatus = issue2.fields.status?.name;
|
|
25856
25928
|
if (jiraStatus) {
|
|
25857
|
-
|
|
25929
|
+
const inSprint = store ? isInActiveSprint(store, fm.tags) : false;
|
|
25930
|
+
const resolved = statusMap ?? {};
|
|
25931
|
+
proposedStatus = artifactType === "task" ? mapJiraStatusForTask(jiraStatus, resolved, inSprint) : mapJiraStatusForAction(jiraStatus, resolved, inSprint);
|
|
25858
25932
|
}
|
|
25859
25933
|
}
|
|
25860
25934
|
marvinArtifacts.push({
|
|
@@ -25983,20 +26057,6 @@ var COMPLEXITY_WEIGHTS = {
|
|
|
25983
26057
|
"very-complex": 8
|
|
25984
26058
|
};
|
|
25985
26059
|
var DEFAULT_WEIGHT = 3;
|
|
25986
|
-
var STATUS_PROGRESS_DEFAULTS = {
|
|
25987
|
-
done: 100,
|
|
25988
|
-
closed: 100,
|
|
25989
|
-
resolved: 100,
|
|
25990
|
-
obsolete: 100,
|
|
25991
|
-
"wont do": 100,
|
|
25992
|
-
cancelled: 100,
|
|
25993
|
-
review: 80,
|
|
25994
|
-
"in-progress": 40,
|
|
25995
|
-
ready: 5,
|
|
25996
|
-
backlog: 0,
|
|
25997
|
-
open: 0
|
|
25998
|
-
};
|
|
25999
|
-
var BLOCKED_DEFAULT_PROGRESS = 10;
|
|
26000
26060
|
function resolveWeight(complexity) {
|
|
26001
26061
|
if (complexity && complexity in COMPLEXITY_WEIGHTS) {
|
|
26002
26062
|
return { weight: COMPLEXITY_WEIGHTS[complexity], weightSource: "complexity" };
|
|
@@ -26012,9 +26072,6 @@ function resolveProgress(frontmatter, commentAnalysisProgress) {
|
|
|
26012
26072
|
return { progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))), progressSource: "comment-analysis" };
|
|
26013
26073
|
}
|
|
26014
26074
|
const status = frontmatter.status;
|
|
26015
|
-
if (status === "blocked") {
|
|
26016
|
-
return { progress: BLOCKED_DEFAULT_PROGRESS, progressSource: "status-default" };
|
|
26017
|
-
}
|
|
26018
26075
|
const defaultProgress = STATUS_PROGRESS_DEFAULTS[status] ?? 0;
|
|
26019
26076
|
return { progress: defaultProgress, progressSource: "status-default" };
|
|
26020
26077
|
}
|
|
@@ -26106,7 +26163,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26106
26163
|
const commentSignals = [];
|
|
26107
26164
|
if (jiraData) {
|
|
26108
26165
|
jiraStatus = jiraData.issue.fields.status.name;
|
|
26109
|
-
|
|
26166
|
+
const inSprint = isInActiveSprint(store, fm.tags);
|
|
26167
|
+
const resolved = options.statusMap ?? {};
|
|
26168
|
+
proposedMarvinStatus = fm.type === "task" ? mapJiraStatusForTask(jiraStatus, resolved, inSprint) : mapJiraStatusForAction(jiraStatus, resolved, inSprint);
|
|
26110
26169
|
const subtasks = jiraData.issue.fields.subtasks ?? [];
|
|
26111
26170
|
if (subtasks.length > 0) {
|
|
26112
26171
|
jiraSubtaskProgress = computeSubtaskProgress(subtasks);
|
|
@@ -26137,6 +26196,20 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
26137
26196
|
proposedValue: jiraSubtaskProgress,
|
|
26138
26197
|
reason: `Jira ${jiraKey} subtask progress is ${jiraSubtaskProgress}%`
|
|
26139
26198
|
});
|
|
26199
|
+
} else if (statusDrift && proposedMarvinStatus && !fm.progressOverride) {
|
|
26200
|
+
const hasExplicitProgress = "progress" in fm && typeof fm.progress === "number";
|
|
26201
|
+
if (!hasExplicitProgress) {
|
|
26202
|
+
const proposedProgress = STATUS_PROGRESS_DEFAULTS[proposedMarvinStatus] ?? 0;
|
|
26203
|
+
if (proposedProgress !== currentProgress) {
|
|
26204
|
+
proposedUpdates.push({
|
|
26205
|
+
artifactId: fm.id,
|
|
26206
|
+
field: "progress",
|
|
26207
|
+
currentValue: currentProgress,
|
|
26208
|
+
proposedValue: proposedProgress,
|
|
26209
|
+
reason: `Status changing to "${proposedMarvinStatus}" \u2192 default progress ${proposedProgress}%`
|
|
26210
|
+
});
|
|
26211
|
+
}
|
|
26212
|
+
}
|
|
26140
26213
|
}
|
|
26141
26214
|
const tags = fm.tags ?? [];
|
|
26142
26215
|
const focusTag = tags.find((t) => t.startsWith("focus:"));
|
|
@@ -26515,7 +26588,7 @@ function findByJiraKey(store, jiraKey) {
|
|
|
26515
26588
|
function createJiraTools(store, projectConfig) {
|
|
26516
26589
|
const jiraUserConfig = loadUserConfig().jira;
|
|
26517
26590
|
const defaultProjectKey = projectConfig?.jira?.projectKey;
|
|
26518
|
-
const statusMap = projectConfig?.jira?.statusMap;
|
|
26591
|
+
const statusMap = normalizeStatusMap(projectConfig?.jira?.statusMap);
|
|
26519
26592
|
return [
|
|
26520
26593
|
// --- Local read tools ---
|
|
26521
26594
|
tool20(
|
|
@@ -27142,15 +27215,32 @@ function createJiraTools(store, projectConfig) {
|
|
|
27142
27215
|
const s = issue2.fields.status.name;
|
|
27143
27216
|
statusCounts.set(s, (statusCounts.get(s) ?? 0) + 1);
|
|
27144
27217
|
}
|
|
27145
|
-
const actionMap = statusMap?.action ?? DEFAULT_ACTION_STATUS_MAP;
|
|
27146
|
-
const taskMap = statusMap?.task ?? DEFAULT_TASK_STATUS_MAP;
|
|
27147
27218
|
const actionLookup = /* @__PURE__ */ new Map();
|
|
27148
|
-
for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
|
|
27149
|
-
for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
|
|
27150
|
-
}
|
|
27151
27219
|
const taskLookup = /* @__PURE__ */ new Map();
|
|
27152
|
-
|
|
27153
|
-
for (const
|
|
27220
|
+
if (statusMap.flat) {
|
|
27221
|
+
for (const [jiraStatus, value] of Object.entries(statusMap.flat)) {
|
|
27222
|
+
const lower = jiraStatus.toLowerCase();
|
|
27223
|
+
if (typeof value === "string") {
|
|
27224
|
+
actionLookup.set(lower, value);
|
|
27225
|
+
taskLookup.set(lower, value);
|
|
27226
|
+
} else {
|
|
27227
|
+
actionLookup.set(lower, value.default);
|
|
27228
|
+
taskLookup.set(lower, value.default);
|
|
27229
|
+
if (value.inSprint) {
|
|
27230
|
+
actionLookup.set(lower, `${value.default} / ${value.inSprint} (inSprint)`);
|
|
27231
|
+
taskLookup.set(lower, `${value.default} / ${value.inSprint} (inSprint)`);
|
|
27232
|
+
}
|
|
27233
|
+
}
|
|
27234
|
+
}
|
|
27235
|
+
} else {
|
|
27236
|
+
const actionMap = statusMap.legacy?.action ?? DEFAULT_ACTION_STATUS_MAP;
|
|
27237
|
+
const taskMap = statusMap.legacy?.task ?? DEFAULT_TASK_STATUS_MAP;
|
|
27238
|
+
for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
|
|
27239
|
+
for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
|
|
27240
|
+
}
|
|
27241
|
+
for (const [marvin, jiraStatuses] of Object.entries(taskMap)) {
|
|
27242
|
+
for (const js of jiraStatuses) taskLookup.set(js.toLowerCase(), marvin);
|
|
27243
|
+
}
|
|
27154
27244
|
}
|
|
27155
27245
|
const parts = [
|
|
27156
27246
|
`Found ${statusCounts.size} distinct statuses in ${resolvedProjectKey} (scanned ${data.issues.length} of ${data.total} issues):`,
|
|
@@ -27171,25 +27261,20 @@ function createJiraTools(store, projectConfig) {
|
|
|
27171
27261
|
if (!taskTarget) unmappedTask.push(status);
|
|
27172
27262
|
}
|
|
27173
27263
|
if (unmappedAction.length > 0 || unmappedTask.length > 0) {
|
|
27264
|
+
const allUnmapped = [.../* @__PURE__ */ new Set([...unmappedAction, ...unmappedTask])];
|
|
27174
27265
|
parts.push("");
|
|
27175
27266
|
parts.push("To fix unmapped statuses, add jira.statusMap to .marvin/config.yaml:");
|
|
27176
27267
|
parts.push(" jira:");
|
|
27177
27268
|
parts.push(" statusMap:");
|
|
27178
|
-
|
|
27179
|
-
parts.push("
|
|
27180
|
-
parts.push(` # Map these: ${unmappedAction.join(", ")}`);
|
|
27181
|
-
parts.push(" # <marvin-status>: [<jira-status>, ...]");
|
|
27182
|
-
}
|
|
27183
|
-
if (unmappedTask.length > 0) {
|
|
27184
|
-
parts.push(" task:");
|
|
27185
|
-
parts.push(` # Map these: ${unmappedTask.join(", ")}`);
|
|
27186
|
-
parts.push(" # <marvin-status>: [<jira-status>, ...]");
|
|
27269
|
+
for (const s of allUnmapped) {
|
|
27270
|
+
parts.push(` "${s}": <marvin-status>`);
|
|
27187
27271
|
}
|
|
27272
|
+
parts.push(" # Supported marvin statuses: done, in-progress, review, ready, blocked, backlog, open");
|
|
27188
27273
|
} else {
|
|
27189
27274
|
parts.push("");
|
|
27190
27275
|
parts.push("All statuses are mapped.");
|
|
27191
27276
|
}
|
|
27192
|
-
const usingConfig = statusMap
|
|
27277
|
+
const usingConfig = statusMap.flat || statusMap.legacy;
|
|
27193
27278
|
parts.push("");
|
|
27194
27279
|
parts.push(usingConfig ? "Using status maps from .marvin/config.yaml." : "Using built-in default status maps (no jira.statusMap in config).");
|
|
27195
27280
|
return {
|
|
@@ -32324,7 +32409,7 @@ async function jiraSyncCommand(artifactId, options = {}) {
|
|
|
32324
32409
|
);
|
|
32325
32410
|
return;
|
|
32326
32411
|
}
|
|
32327
|
-
const statusMap = project.config.jira?.statusMap;
|
|
32412
|
+
const statusMap = normalizeStatusMap(project.config.jira?.statusMap);
|
|
32328
32413
|
const label = artifactId ? `Checking ${artifactId} against Jira...` : "Checking all Jira-linked actions/tasks...";
|
|
32329
32414
|
console.log(chalk20.dim(label));
|
|
32330
32415
|
if (options.dryRun) {
|
|
@@ -32441,9 +32526,7 @@ async function jiraStatusesCommand(projectKey) {
|
|
|
32441
32526
|
return;
|
|
32442
32527
|
}
|
|
32443
32528
|
console.log(chalk20.dim(`Fetching statuses from Jira project ${resolvedProjectKey}...`));
|
|
32444
|
-
const statusMap = project.config.jira?.statusMap;
|
|
32445
|
-
const actionMap = statusMap?.action ?? DEFAULT_ACTION_STATUS_MAP;
|
|
32446
|
-
const taskMap = statusMap?.task ?? DEFAULT_TASK_STATUS_MAP;
|
|
32529
|
+
const statusMap = normalizeStatusMap(project.config.jira?.statusMap);
|
|
32447
32530
|
const email3 = jiraUserConfig?.email ?? process.env.JIRA_EMAIL;
|
|
32448
32531
|
const apiToken = jiraUserConfig?.apiToken ?? process.env.JIRA_API_TOKEN;
|
|
32449
32532
|
const auth = "Basic " + Buffer.from(`${email3}:${apiToken}`).toString("base64");
|
|
@@ -32467,12 +32550,28 @@ async function jiraStatusesCommand(projectKey) {
|
|
|
32467
32550
|
statusCounts.set(s, (statusCounts.get(s) ?? 0) + 1);
|
|
32468
32551
|
}
|
|
32469
32552
|
const actionLookup = /* @__PURE__ */ new Map();
|
|
32470
|
-
for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
|
|
32471
|
-
for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
|
|
32472
|
-
}
|
|
32473
32553
|
const taskLookup = /* @__PURE__ */ new Map();
|
|
32474
|
-
|
|
32475
|
-
for (const
|
|
32554
|
+
if (statusMap.flat) {
|
|
32555
|
+
for (const [jiraStatus, value] of Object.entries(statusMap.flat)) {
|
|
32556
|
+
const lower = jiraStatus.toLowerCase();
|
|
32557
|
+
if (typeof value === "string") {
|
|
32558
|
+
actionLookup.set(lower, value);
|
|
32559
|
+
taskLookup.set(lower, value);
|
|
32560
|
+
} else {
|
|
32561
|
+
const label = value.inSprint ? `${value.default} / ${value.inSprint} (inSprint)` : value.default;
|
|
32562
|
+
actionLookup.set(lower, label);
|
|
32563
|
+
taskLookup.set(lower, label);
|
|
32564
|
+
}
|
|
32565
|
+
}
|
|
32566
|
+
} else {
|
|
32567
|
+
const actionMap = statusMap.legacy?.action ?? DEFAULT_ACTION_STATUS_MAP;
|
|
32568
|
+
const taskMap = statusMap.legacy?.task ?? DEFAULT_TASK_STATUS_MAP;
|
|
32569
|
+
for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
|
|
32570
|
+
for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
|
|
32571
|
+
}
|
|
32572
|
+
for (const [marvin, jiraStatuses] of Object.entries(taskMap)) {
|
|
32573
|
+
for (const js of jiraStatuses) taskLookup.set(js.toLowerCase(), marvin);
|
|
32574
|
+
}
|
|
32476
32575
|
}
|
|
32477
32576
|
console.log(
|
|
32478
32577
|
`
|
|
@@ -32495,14 +32594,11 @@ Found ${chalk20.bold(String(statusCounts.size))} distinct statuses in ${chalk20.
|
|
|
32495
32594
|
console.log(chalk20.yellow("\nSome statuses are unmapped. Add jira.statusMap to .marvin/config.yaml:"));
|
|
32496
32595
|
console.log(chalk20.dim(" jira:"));
|
|
32497
32596
|
console.log(chalk20.dim(" statusMap:"));
|
|
32498
|
-
console.log(chalk20.dim("
|
|
32499
|
-
console.log(chalk20.dim(' <marvin-status>: ["<Jira Status>", ...]'));
|
|
32500
|
-
console.log(chalk20.dim(" task:"));
|
|
32501
|
-
console.log(chalk20.dim(' <marvin-status>: ["<Jira Status>", ...]'));
|
|
32597
|
+
console.log(chalk20.dim(' "<Jira Status>": <marvin-status>'));
|
|
32502
32598
|
} else {
|
|
32503
32599
|
console.log(chalk20.green("\nAll statuses are mapped."));
|
|
32504
32600
|
}
|
|
32505
|
-
const usingConfig = statusMap
|
|
32601
|
+
const usingConfig = statusMap.flat || statusMap.legacy;
|
|
32506
32602
|
console.log(
|
|
32507
32603
|
chalk20.dim(
|
|
32508
32604
|
usingConfig ? "\nUsing status maps from .marvin/config.yaml." : "\nUsing built-in default status maps (no jira.statusMap in config)."
|
|
@@ -32537,7 +32633,7 @@ async function jiraDailyCommand(options) {
|
|
|
32537
32633
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
32538
32634
|
const fromDate = options.from ?? today;
|
|
32539
32635
|
const toDate = options.to ?? fromDate;
|
|
32540
|
-
const statusMap = proj.config.jira?.statusMap;
|
|
32636
|
+
const statusMap = normalizeStatusMap(proj.config.jira?.statusMap);
|
|
32541
32637
|
const rangeLabel = fromDate === toDate ? fromDate : `${fromDate} to ${toDate}`;
|
|
32542
32638
|
console.log(
|
|
32543
32639
|
chalk20.dim(`Fetching Jira daily summary for ${resolvedProjectKey} \u2014 ${rangeLabel}...`)
|
|
@@ -32654,7 +32750,7 @@ function createProgram() {
|
|
|
32654
32750
|
const program2 = new Command();
|
|
32655
32751
|
program2.name("marvin").description(
|
|
32656
32752
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
32657
|
-
).version("0.5.
|
|
32753
|
+
).version("0.5.17");
|
|
32658
32754
|
program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
32659
32755
|
await initCommand();
|
|
32660
32756
|
});
|