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-serve.js
CHANGED
|
@@ -14349,11 +14349,25 @@ import { tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
|
|
|
14349
14349
|
|
|
14350
14350
|
// src/storage/progress.ts
|
|
14351
14351
|
var DONE_STATUSES = /* @__PURE__ */ new Set(["done", "closed", "resolved", "cancelled"]);
|
|
14352
|
+
var STATUS_PROGRESS_DEFAULTS = {
|
|
14353
|
+
done: 100,
|
|
14354
|
+
closed: 100,
|
|
14355
|
+
resolved: 100,
|
|
14356
|
+
obsolete: 100,
|
|
14357
|
+
"wont do": 100,
|
|
14358
|
+
cancelled: 100,
|
|
14359
|
+
review: 80,
|
|
14360
|
+
"in-progress": 40,
|
|
14361
|
+
ready: 5,
|
|
14362
|
+
blocked: 10,
|
|
14363
|
+
backlog: 0,
|
|
14364
|
+
open: 0
|
|
14365
|
+
};
|
|
14352
14366
|
function getEffectiveProgress(frontmatter) {
|
|
14353
14367
|
if (DONE_STATUSES.has(frontmatter.status)) return 100;
|
|
14354
14368
|
const raw = frontmatter.progress;
|
|
14355
14369
|
if (typeof raw === "number") return Math.max(0, Math.min(100, Math.round(raw)));
|
|
14356
|
-
return 0;
|
|
14370
|
+
return STATUS_PROGRESS_DEFAULTS[frontmatter.status] ?? 0;
|
|
14357
14371
|
}
|
|
14358
14372
|
function propagateProgressFromTask(store, taskId) {
|
|
14359
14373
|
const updated = [];
|
|
@@ -19178,24 +19192,80 @@ var DEFAULT_TASK_STATUS_MAP = {
|
|
|
19178
19192
|
blocked: ["Blocked"],
|
|
19179
19193
|
backlog: ["To Do", "Open", "Backlog", "New"]
|
|
19180
19194
|
};
|
|
19181
|
-
function
|
|
19182
|
-
|
|
19195
|
+
function isLegacyFormat(statusMap) {
|
|
19196
|
+
if (!statusMap || typeof statusMap !== "object") return false;
|
|
19197
|
+
const keys = Object.keys(statusMap);
|
|
19198
|
+
if (!keys.every((k) => k === "action" || k === "task")) return false;
|
|
19199
|
+
for (const key of keys) {
|
|
19200
|
+
const val = statusMap[key];
|
|
19201
|
+
if (typeof val !== "object" || val === null) return false;
|
|
19202
|
+
for (const innerVal of Object.values(val)) {
|
|
19203
|
+
if (!Array.isArray(innerVal)) return false;
|
|
19204
|
+
if (!innerVal.every((v) => typeof v === "string")) return false;
|
|
19205
|
+
}
|
|
19206
|
+
}
|
|
19207
|
+
return true;
|
|
19208
|
+
}
|
|
19209
|
+
function buildLegacyLookup(legacyMap) {
|
|
19183
19210
|
const lookup = /* @__PURE__ */ new Map();
|
|
19184
|
-
for (const [marvinStatus, jiraStatuses] of Object.entries(
|
|
19211
|
+
for (const [marvinStatus, jiraStatuses] of Object.entries(legacyMap)) {
|
|
19185
19212
|
for (const js of jiraStatuses) {
|
|
19186
19213
|
lookup.set(js.toLowerCase(), marvinStatus);
|
|
19187
19214
|
}
|
|
19188
19215
|
}
|
|
19189
19216
|
return lookup;
|
|
19190
19217
|
}
|
|
19191
|
-
function
|
|
19192
|
-
const lookup =
|
|
19218
|
+
function buildFlatLookup(flatMap, inSprint) {
|
|
19219
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
19220
|
+
for (const [jiraStatus, value] of Object.entries(flatMap)) {
|
|
19221
|
+
if (typeof value === "string") {
|
|
19222
|
+
lookup.set(jiraStatus.toLowerCase(), value);
|
|
19223
|
+
} else {
|
|
19224
|
+
const resolved = inSprint && value.inSprint ? value.inSprint : value.default;
|
|
19225
|
+
lookup.set(jiraStatus.toLowerCase(), resolved);
|
|
19226
|
+
}
|
|
19227
|
+
}
|
|
19228
|
+
return lookup;
|
|
19229
|
+
}
|
|
19230
|
+
function normalizeStatusMap(statusMap) {
|
|
19231
|
+
if (!statusMap) return {};
|
|
19232
|
+
if (isLegacyFormat(statusMap)) {
|
|
19233
|
+
return { legacy: statusMap };
|
|
19234
|
+
}
|
|
19235
|
+
return { flat: statusMap };
|
|
19236
|
+
}
|
|
19237
|
+
function mapJiraStatusForAction(status, resolved, inSprint = false) {
|
|
19238
|
+
if (resolved.flat) {
|
|
19239
|
+
const lookup2 = buildFlatLookup(resolved.flat, inSprint);
|
|
19240
|
+
return lookup2.get(status.toLowerCase()) ?? "open";
|
|
19241
|
+
}
|
|
19242
|
+
const lookup = buildLegacyLookup(resolved.legacy?.action ?? DEFAULT_ACTION_STATUS_MAP);
|
|
19193
19243
|
return lookup.get(status.toLowerCase()) ?? "open";
|
|
19194
19244
|
}
|
|
19195
|
-
function mapJiraStatusForTask(status,
|
|
19196
|
-
|
|
19245
|
+
function mapJiraStatusForTask(status, resolved, inSprint = false) {
|
|
19246
|
+
if (resolved.flat) {
|
|
19247
|
+
const lookup2 = buildFlatLookup(resolved.flat, inSprint);
|
|
19248
|
+
return lookup2.get(status.toLowerCase()) ?? "backlog";
|
|
19249
|
+
}
|
|
19250
|
+
const lookup = buildLegacyLookup(resolved.legacy?.task ?? DEFAULT_TASK_STATUS_MAP);
|
|
19197
19251
|
return lookup.get(status.toLowerCase()) ?? "backlog";
|
|
19198
19252
|
}
|
|
19253
|
+
function isInActiveSprint(store, tags) {
|
|
19254
|
+
if (!tags) return false;
|
|
19255
|
+
const sprintTags = tags.filter((t) => t.startsWith("sprint:"));
|
|
19256
|
+
if (sprintTags.length === 0) return false;
|
|
19257
|
+
for (const tag of sprintTags) {
|
|
19258
|
+
const sprintId = tag.slice(7);
|
|
19259
|
+
const sprintDoc = store.get(sprintId);
|
|
19260
|
+
if (sprintDoc) {
|
|
19261
|
+
const status = sprintDoc.frontmatter.status;
|
|
19262
|
+
if (status === "active" || status === "completed") {
|
|
19263
|
+
return true;
|
|
19264
|
+
}
|
|
19265
|
+
}
|
|
19266
|
+
}
|
|
19267
|
+
return false;
|
|
19268
|
+
}
|
|
19199
19269
|
function extractJiraKeyFromTags(tags) {
|
|
19200
19270
|
if (!tags) return void 0;
|
|
19201
19271
|
const tag = tags.find((t) => /^jira:[A-Z]+-\d+$/i.test(t));
|
|
@@ -19237,7 +19307,9 @@ async function fetchJiraStatus(store, client, host, artifactId, statusMap) {
|
|
|
19237
19307
|
const artifactType = doc.frontmatter.type;
|
|
19238
19308
|
try {
|
|
19239
19309
|
const issue2 = await client.getIssueWithLinks(jiraKey);
|
|
19240
|
-
const
|
|
19310
|
+
const inSprint = isInActiveSprint(store, doc.frontmatter.tags);
|
|
19311
|
+
const resolved = statusMap ?? {};
|
|
19312
|
+
const proposedStatus = artifactType === "task" ? mapJiraStatusForTask(issue2.fields.status.name, resolved, inSprint) : mapJiraStatusForAction(issue2.fields.status.name, resolved, inSprint);
|
|
19241
19313
|
const currentStatus = doc.frontmatter.status;
|
|
19242
19314
|
const linkedIssues = [];
|
|
19243
19315
|
if (issue2.fields.subtasks) {
|
|
@@ -19549,7 +19621,7 @@ async function fetchJiraDaily(store, client, host, projectKey, dateRange, status
|
|
|
19549
19621
|
const batch = issues.slice(i, i + BATCH_SIZE2);
|
|
19550
19622
|
const results = await Promise.allSettled(
|
|
19551
19623
|
batch.map(
|
|
19552
|
-
(issue2) => processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap)
|
|
19624
|
+
(issue2) => processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap, store)
|
|
19553
19625
|
)
|
|
19554
19626
|
);
|
|
19555
19627
|
for (let j = 0; j < results.length; j++) {
|
|
@@ -19566,7 +19638,7 @@ async function fetchJiraDaily(store, client, host, projectKey, dateRange, status
|
|
|
19566
19638
|
summary.proposedActions = generateProposedActions(summary.issues);
|
|
19567
19639
|
return summary;
|
|
19568
19640
|
}
|
|
19569
|
-
async function processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap) {
|
|
19641
|
+
async function processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts, allDocs, statusMap, store) {
|
|
19570
19642
|
const [changelogResult, commentsResult, remoteLinksResult, issueWithLinks] = await Promise.all([
|
|
19571
19643
|
client.getChangelog(issue2.key).catch(() => []),
|
|
19572
19644
|
client.getComments(issue2.key).catch(() => []),
|
|
@@ -19654,7 +19726,9 @@ async function processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts,
|
|
|
19654
19726
|
if (artifactType === "action" || artifactType === "task") {
|
|
19655
19727
|
const jiraStatus = issue2.fields.status?.name;
|
|
19656
19728
|
if (jiraStatus) {
|
|
19657
|
-
|
|
19729
|
+
const inSprint = store ? isInActiveSprint(store, fm.tags) : false;
|
|
19730
|
+
const resolved = statusMap ?? {};
|
|
19731
|
+
proposedStatus = artifactType === "task" ? mapJiraStatusForTask(jiraStatus, resolved, inSprint) : mapJiraStatusForAction(jiraStatus, resolved, inSprint);
|
|
19658
19732
|
}
|
|
19659
19733
|
}
|
|
19660
19734
|
marvinArtifacts.push({
|
|
@@ -19783,20 +19857,6 @@ var COMPLEXITY_WEIGHTS = {
|
|
|
19783
19857
|
"very-complex": 8
|
|
19784
19858
|
};
|
|
19785
19859
|
var DEFAULT_WEIGHT = 3;
|
|
19786
|
-
var STATUS_PROGRESS_DEFAULTS = {
|
|
19787
|
-
done: 100,
|
|
19788
|
-
closed: 100,
|
|
19789
|
-
resolved: 100,
|
|
19790
|
-
obsolete: 100,
|
|
19791
|
-
"wont do": 100,
|
|
19792
|
-
cancelled: 100,
|
|
19793
|
-
review: 80,
|
|
19794
|
-
"in-progress": 40,
|
|
19795
|
-
ready: 5,
|
|
19796
|
-
backlog: 0,
|
|
19797
|
-
open: 0
|
|
19798
|
-
};
|
|
19799
|
-
var BLOCKED_DEFAULT_PROGRESS = 10;
|
|
19800
19860
|
function resolveWeight(complexity) {
|
|
19801
19861
|
if (complexity && complexity in COMPLEXITY_WEIGHTS) {
|
|
19802
19862
|
return { weight: COMPLEXITY_WEIGHTS[complexity], weightSource: "complexity" };
|
|
@@ -19812,9 +19872,6 @@ function resolveProgress(frontmatter, commentAnalysisProgress) {
|
|
|
19812
19872
|
return { progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))), progressSource: "comment-analysis" };
|
|
19813
19873
|
}
|
|
19814
19874
|
const status = frontmatter.status;
|
|
19815
|
-
if (status === "blocked") {
|
|
19816
|
-
return { progress: BLOCKED_DEFAULT_PROGRESS, progressSource: "status-default" };
|
|
19817
|
-
}
|
|
19818
19875
|
const defaultProgress = STATUS_PROGRESS_DEFAULTS[status] ?? 0;
|
|
19819
19876
|
return { progress: defaultProgress, progressSource: "status-default" };
|
|
19820
19877
|
}
|
|
@@ -19906,7 +19963,9 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
19906
19963
|
const commentSignals = [];
|
|
19907
19964
|
if (jiraData) {
|
|
19908
19965
|
jiraStatus = jiraData.issue.fields.status.name;
|
|
19909
|
-
|
|
19966
|
+
const inSprint = isInActiveSprint(store, fm.tags);
|
|
19967
|
+
const resolved = options.statusMap ?? {};
|
|
19968
|
+
proposedMarvinStatus = fm.type === "task" ? mapJiraStatusForTask(jiraStatus, resolved, inSprint) : mapJiraStatusForAction(jiraStatus, resolved, inSprint);
|
|
19910
19969
|
const subtasks = jiraData.issue.fields.subtasks ?? [];
|
|
19911
19970
|
if (subtasks.length > 0) {
|
|
19912
19971
|
jiraSubtaskProgress = computeSubtaskProgress(subtasks);
|
|
@@ -19937,6 +19996,20 @@ async function assessSprintProgress(store, client, host, options = {}) {
|
|
|
19937
19996
|
proposedValue: jiraSubtaskProgress,
|
|
19938
19997
|
reason: `Jira ${jiraKey} subtask progress is ${jiraSubtaskProgress}%`
|
|
19939
19998
|
});
|
|
19999
|
+
} else if (statusDrift && proposedMarvinStatus && !fm.progressOverride) {
|
|
20000
|
+
const hasExplicitProgress = "progress" in fm && typeof fm.progress === "number";
|
|
20001
|
+
if (!hasExplicitProgress) {
|
|
20002
|
+
const proposedProgress = STATUS_PROGRESS_DEFAULTS[proposedMarvinStatus] ?? 0;
|
|
20003
|
+
if (proposedProgress !== currentProgress) {
|
|
20004
|
+
proposedUpdates.push({
|
|
20005
|
+
artifactId: fm.id,
|
|
20006
|
+
field: "progress",
|
|
20007
|
+
currentValue: currentProgress,
|
|
20008
|
+
proposedValue: proposedProgress,
|
|
20009
|
+
reason: `Status changing to "${proposedMarvinStatus}" \u2192 default progress ${proposedProgress}%`
|
|
20010
|
+
});
|
|
20011
|
+
}
|
|
20012
|
+
}
|
|
19940
20013
|
}
|
|
19941
20014
|
const tags = fm.tags ?? [];
|
|
19942
20015
|
const focusTag = tags.find((t) => t.startsWith("focus:"));
|
|
@@ -20315,7 +20388,7 @@ function findByJiraKey(store, jiraKey) {
|
|
|
20315
20388
|
function createJiraTools(store, projectConfig) {
|
|
20316
20389
|
const jiraUserConfig = loadUserConfig().jira;
|
|
20317
20390
|
const defaultProjectKey = projectConfig?.jira?.projectKey;
|
|
20318
|
-
const statusMap = projectConfig?.jira?.statusMap;
|
|
20391
|
+
const statusMap = normalizeStatusMap(projectConfig?.jira?.statusMap);
|
|
20319
20392
|
return [
|
|
20320
20393
|
// --- Local read tools ---
|
|
20321
20394
|
tool20(
|
|
@@ -20942,15 +21015,32 @@ function createJiraTools(store, projectConfig) {
|
|
|
20942
21015
|
const s = issue2.fields.status.name;
|
|
20943
21016
|
statusCounts.set(s, (statusCounts.get(s) ?? 0) + 1);
|
|
20944
21017
|
}
|
|
20945
|
-
const actionMap = statusMap?.action ?? DEFAULT_ACTION_STATUS_MAP;
|
|
20946
|
-
const taskMap = statusMap?.task ?? DEFAULT_TASK_STATUS_MAP;
|
|
20947
21018
|
const actionLookup = /* @__PURE__ */ new Map();
|
|
20948
|
-
for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
|
|
20949
|
-
for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
|
|
20950
|
-
}
|
|
20951
21019
|
const taskLookup = /* @__PURE__ */ new Map();
|
|
20952
|
-
|
|
20953
|
-
for (const
|
|
21020
|
+
if (statusMap.flat) {
|
|
21021
|
+
for (const [jiraStatus, value] of Object.entries(statusMap.flat)) {
|
|
21022
|
+
const lower = jiraStatus.toLowerCase();
|
|
21023
|
+
if (typeof value === "string") {
|
|
21024
|
+
actionLookup.set(lower, value);
|
|
21025
|
+
taskLookup.set(lower, value);
|
|
21026
|
+
} else {
|
|
21027
|
+
actionLookup.set(lower, value.default);
|
|
21028
|
+
taskLookup.set(lower, value.default);
|
|
21029
|
+
if (value.inSprint) {
|
|
21030
|
+
actionLookup.set(lower, `${value.default} / ${value.inSprint} (inSprint)`);
|
|
21031
|
+
taskLookup.set(lower, `${value.default} / ${value.inSprint} (inSprint)`);
|
|
21032
|
+
}
|
|
21033
|
+
}
|
|
21034
|
+
}
|
|
21035
|
+
} else {
|
|
21036
|
+
const actionMap = statusMap.legacy?.action ?? DEFAULT_ACTION_STATUS_MAP;
|
|
21037
|
+
const taskMap = statusMap.legacy?.task ?? DEFAULT_TASK_STATUS_MAP;
|
|
21038
|
+
for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
|
|
21039
|
+
for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
|
|
21040
|
+
}
|
|
21041
|
+
for (const [marvin, jiraStatuses] of Object.entries(taskMap)) {
|
|
21042
|
+
for (const js of jiraStatuses) taskLookup.set(js.toLowerCase(), marvin);
|
|
21043
|
+
}
|
|
20954
21044
|
}
|
|
20955
21045
|
const parts = [
|
|
20956
21046
|
`Found ${statusCounts.size} distinct statuses in ${resolvedProjectKey} (scanned ${data.issues.length} of ${data.total} issues):`,
|
|
@@ -20971,25 +21061,20 @@ function createJiraTools(store, projectConfig) {
|
|
|
20971
21061
|
if (!taskTarget) unmappedTask.push(status);
|
|
20972
21062
|
}
|
|
20973
21063
|
if (unmappedAction.length > 0 || unmappedTask.length > 0) {
|
|
21064
|
+
const allUnmapped = [.../* @__PURE__ */ new Set([...unmappedAction, ...unmappedTask])];
|
|
20974
21065
|
parts.push("");
|
|
20975
21066
|
parts.push("To fix unmapped statuses, add jira.statusMap to .marvin/config.yaml:");
|
|
20976
21067
|
parts.push(" jira:");
|
|
20977
21068
|
parts.push(" statusMap:");
|
|
20978
|
-
|
|
20979
|
-
parts.push("
|
|
20980
|
-
parts.push(` # Map these: ${unmappedAction.join(", ")}`);
|
|
20981
|
-
parts.push(" # <marvin-status>: [<jira-status>, ...]");
|
|
20982
|
-
}
|
|
20983
|
-
if (unmappedTask.length > 0) {
|
|
20984
|
-
parts.push(" task:");
|
|
20985
|
-
parts.push(` # Map these: ${unmappedTask.join(", ")}`);
|
|
20986
|
-
parts.push(" # <marvin-status>: [<jira-status>, ...]");
|
|
21069
|
+
for (const s of allUnmapped) {
|
|
21070
|
+
parts.push(` "${s}": <marvin-status>`);
|
|
20987
21071
|
}
|
|
21072
|
+
parts.push(" # Supported marvin statuses: done, in-progress, review, ready, blocked, backlog, open");
|
|
20988
21073
|
} else {
|
|
20989
21074
|
parts.push("");
|
|
20990
21075
|
parts.push("All statuses are mapped.");
|
|
20991
21076
|
}
|
|
20992
|
-
const usingConfig = statusMap
|
|
21077
|
+
const usingConfig = statusMap.flat || statusMap.legacy;
|
|
20993
21078
|
parts.push("");
|
|
20994
21079
|
parts.push(usingConfig ? "Using status maps from .marvin/config.yaml." : "Using built-in default status maps (no jira.statusMap in config).");
|
|
20995
21080
|
return {
|