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/index.d.ts
CHANGED
|
@@ -16,14 +16,39 @@ interface MarvinUserConfig {
|
|
|
16
16
|
interface GitConfig {
|
|
17
17
|
remote?: string;
|
|
18
18
|
}
|
|
19
|
-
|
|
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?:
|
|
26
|
-
task?:
|
|
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
|
|
25096
|
-
|
|
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(
|
|
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
|
|
25106
|
-
const lookup =
|
|
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,
|
|
25110
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26907
|
-
for (const
|
|
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
|
-
|
|
26933
|
-
parts.push("
|
|
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
|
|
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
|
-
|
|
32483
|
-
for (const
|
|
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("
|
|
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
|
|
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.
|
|
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
|
});
|