mrvn-cli 0.2.5 → 0.2.8
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/README.md +103 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +969 -92
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +836 -65
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +994 -117
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin.js
CHANGED
|
@@ -14101,6 +14101,128 @@ function createMeetingTools(store) {
|
|
|
14101
14101
|
|
|
14102
14102
|
// src/plugins/builtin/tools/reports.ts
|
|
14103
14103
|
import { tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
|
|
14104
|
+
|
|
14105
|
+
// src/reports/gar/collector.ts
|
|
14106
|
+
function collectGarMetrics(store) {
|
|
14107
|
+
const allActions = store.list({ type: "action" });
|
|
14108
|
+
const openActions = allActions.filter((d) => d.frontmatter.status === "open");
|
|
14109
|
+
const doneActions = allActions.filter((d) => d.frontmatter.status === "done");
|
|
14110
|
+
const allDocs = store.list();
|
|
14111
|
+
const blockedItems = allDocs.filter(
|
|
14112
|
+
(d) => d.frontmatter.tags?.includes("blocked")
|
|
14113
|
+
);
|
|
14114
|
+
const overdueItems = allDocs.filter(
|
|
14115
|
+
(d) => d.frontmatter.tags?.includes("overdue")
|
|
14116
|
+
);
|
|
14117
|
+
const openQuestions = store.list({ type: "question", status: "open" });
|
|
14118
|
+
const riskItems = allDocs.filter(
|
|
14119
|
+
(d) => d.frontmatter.tags?.includes("risk")
|
|
14120
|
+
);
|
|
14121
|
+
const unownedActions = openActions.filter((d) => !d.frontmatter.owner);
|
|
14122
|
+
const total = allActions.length;
|
|
14123
|
+
const done = doneActions.length;
|
|
14124
|
+
const completionPct = total > 0 ? Math.round(done / total * 100) : 100;
|
|
14125
|
+
const scheduleItems = [
|
|
14126
|
+
...blockedItems,
|
|
14127
|
+
...overdueItems
|
|
14128
|
+
].filter(
|
|
14129
|
+
(d, i, arr) => arr.findIndex((x) => x.frontmatter.id === d.frontmatter.id) === i
|
|
14130
|
+
).map((d) => ({ id: d.frontmatter.id, title: d.frontmatter.title }));
|
|
14131
|
+
const qualityItems = [
|
|
14132
|
+
...riskItems,
|
|
14133
|
+
...openQuestions
|
|
14134
|
+
].filter(
|
|
14135
|
+
(d, i, arr) => arr.findIndex((x) => x.frontmatter.id === d.frontmatter.id) === i
|
|
14136
|
+
).map((d) => ({ id: d.frontmatter.id, title: d.frontmatter.title }));
|
|
14137
|
+
const resourceItems = unownedActions.map((d) => ({
|
|
14138
|
+
id: d.frontmatter.id,
|
|
14139
|
+
title: d.frontmatter.title
|
|
14140
|
+
}));
|
|
14141
|
+
return {
|
|
14142
|
+
scope: {
|
|
14143
|
+
total,
|
|
14144
|
+
open: openActions.length,
|
|
14145
|
+
done,
|
|
14146
|
+
completionPct
|
|
14147
|
+
},
|
|
14148
|
+
schedule: {
|
|
14149
|
+
blocked: blockedItems.length,
|
|
14150
|
+
overdue: overdueItems.length,
|
|
14151
|
+
items: scheduleItems
|
|
14152
|
+
},
|
|
14153
|
+
quality: {
|
|
14154
|
+
risks: riskItems.length,
|
|
14155
|
+
openQuestions: openQuestions.length,
|
|
14156
|
+
items: qualityItems
|
|
14157
|
+
},
|
|
14158
|
+
resources: {
|
|
14159
|
+
unowned: unownedActions.length,
|
|
14160
|
+
items: resourceItems
|
|
14161
|
+
}
|
|
14162
|
+
};
|
|
14163
|
+
}
|
|
14164
|
+
|
|
14165
|
+
// src/reports/gar/evaluator.ts
|
|
14166
|
+
function worstStatus(statuses) {
|
|
14167
|
+
if (statuses.includes("red")) return "red";
|
|
14168
|
+
if (statuses.includes("amber")) return "amber";
|
|
14169
|
+
return "green";
|
|
14170
|
+
}
|
|
14171
|
+
function evaluateGar(projectName, metrics) {
|
|
14172
|
+
const areas = [];
|
|
14173
|
+
const scopePct = metrics.scope.completionPct;
|
|
14174
|
+
const scopeStatus = scopePct >= 70 ? "green" : scopePct >= 40 ? "amber" : "red";
|
|
14175
|
+
areas.push({
|
|
14176
|
+
name: "Scope",
|
|
14177
|
+
status: scopeStatus,
|
|
14178
|
+
summary: `${scopePct}% complete (${metrics.scope.done}/${metrics.scope.total})`,
|
|
14179
|
+
items: []
|
|
14180
|
+
});
|
|
14181
|
+
const scheduleCount = metrics.schedule.blocked + metrics.schedule.overdue;
|
|
14182
|
+
const scheduleStatus = scheduleCount === 0 ? "green" : scheduleCount <= 2 ? "amber" : "red";
|
|
14183
|
+
const scheduleParts = [];
|
|
14184
|
+
if (metrics.schedule.blocked > 0)
|
|
14185
|
+
scheduleParts.push(`${metrics.schedule.blocked} blocked`);
|
|
14186
|
+
if (metrics.schedule.overdue > 0)
|
|
14187
|
+
scheduleParts.push(`${metrics.schedule.overdue} overdue`);
|
|
14188
|
+
areas.push({
|
|
14189
|
+
name: "Schedule",
|
|
14190
|
+
status: scheduleStatus,
|
|
14191
|
+
summary: scheduleParts.length > 0 ? scheduleParts.join(", ") : "on track",
|
|
14192
|
+
items: metrics.schedule.items
|
|
14193
|
+
});
|
|
14194
|
+
const qualityCount = metrics.quality.risks + metrics.quality.openQuestions;
|
|
14195
|
+
const qualityStatus = qualityCount === 0 ? "green" : qualityCount <= 2 ? "amber" : "red";
|
|
14196
|
+
const qualityParts = [];
|
|
14197
|
+
if (metrics.quality.risks > 0)
|
|
14198
|
+
qualityParts.push(`${metrics.quality.risks} risk(s)`);
|
|
14199
|
+
if (metrics.quality.openQuestions > 0)
|
|
14200
|
+
qualityParts.push(`${metrics.quality.openQuestions} open question(s)`);
|
|
14201
|
+
areas.push({
|
|
14202
|
+
name: "Quality",
|
|
14203
|
+
status: qualityStatus,
|
|
14204
|
+
summary: qualityParts.length > 0 ? qualityParts.join(", ") : "no issues",
|
|
14205
|
+
items: metrics.quality.items
|
|
14206
|
+
});
|
|
14207
|
+
const resourceCount = metrics.resources.unowned;
|
|
14208
|
+
const resourceStatus = resourceCount === 0 ? "green" : resourceCount <= 2 ? "amber" : "red";
|
|
14209
|
+
areas.push({
|
|
14210
|
+
name: "Resources",
|
|
14211
|
+
status: resourceStatus,
|
|
14212
|
+
summary: resourceCount > 0 ? `${resourceCount} unowned action(s)` : "all assigned",
|
|
14213
|
+
items: metrics.resources.items
|
|
14214
|
+
});
|
|
14215
|
+
const overall = worstStatus(areas.map((a) => a.status));
|
|
14216
|
+
return {
|
|
14217
|
+
projectName,
|
|
14218
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
14219
|
+
overall,
|
|
14220
|
+
areas,
|
|
14221
|
+
metrics
|
|
14222
|
+
};
|
|
14223
|
+
}
|
|
14224
|
+
|
|
14225
|
+
// src/plugins/builtin/tools/reports.ts
|
|
14104
14226
|
function createReportTools(store) {
|
|
14105
14227
|
return [
|
|
14106
14228
|
tool2(
|
|
@@ -14189,41 +14311,10 @@ function createReportTools(store) {
|
|
|
14189
14311
|
"Generate a Green-Amber-Red report with metrics across scope, schedule, quality, and resources",
|
|
14190
14312
|
{},
|
|
14191
14313
|
async () => {
|
|
14192
|
-
const
|
|
14193
|
-
const
|
|
14194
|
-
const doneActions = allActions.filter((d) => d.frontmatter.status === "done");
|
|
14195
|
-
const allDocs = store.list();
|
|
14196
|
-
const blockedItems = allDocs.filter(
|
|
14197
|
-
(d) => d.frontmatter.tags?.includes("blocked")
|
|
14198
|
-
);
|
|
14199
|
-
const overdueItems = allDocs.filter(
|
|
14200
|
-
(d) => d.frontmatter.tags?.includes("overdue")
|
|
14201
|
-
);
|
|
14202
|
-
const openQuestions = store.list({ type: "question", status: "open" });
|
|
14203
|
-
const riskItems = allDocs.filter(
|
|
14204
|
-
(d) => d.frontmatter.tags?.includes("risk")
|
|
14205
|
-
);
|
|
14206
|
-
const unownedActions = openActions.filter((d) => !d.frontmatter.owner);
|
|
14207
|
-
const areas = {
|
|
14208
|
-
scope: {
|
|
14209
|
-
total: allActions.length,
|
|
14210
|
-
open: openActions.length,
|
|
14211
|
-
done: doneActions.length
|
|
14212
|
-
},
|
|
14213
|
-
schedule: {
|
|
14214
|
-
blocked: blockedItems.length,
|
|
14215
|
-
overdue: overdueItems.length
|
|
14216
|
-
},
|
|
14217
|
-
quality: {
|
|
14218
|
-
openQuestions: openQuestions.length,
|
|
14219
|
-
risks: riskItems.length
|
|
14220
|
-
},
|
|
14221
|
-
resources: {
|
|
14222
|
-
unowned: unownedActions.length
|
|
14223
|
-
}
|
|
14224
|
-
};
|
|
14314
|
+
const metrics = collectGarMetrics(store);
|
|
14315
|
+
const report = evaluateGar("project", metrics);
|
|
14225
14316
|
return {
|
|
14226
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
14317
|
+
content: [{ type: "text", text: JSON.stringify(report, null, 2) }]
|
|
14227
14318
|
};
|
|
14228
14319
|
},
|
|
14229
14320
|
{ annotations: { readOnly: true } }
|
|
@@ -15003,6 +15094,188 @@ function createSprintTools(store) {
|
|
|
15003
15094
|
];
|
|
15004
15095
|
}
|
|
15005
15096
|
|
|
15097
|
+
// src/plugins/builtin/tools/sprint-planning.ts
|
|
15098
|
+
import { tool as tool7 } from "@anthropic-ai/claude-agent-sdk";
|
|
15099
|
+
var PRIORITY_ORDER = {
|
|
15100
|
+
critical: 0,
|
|
15101
|
+
high: 1,
|
|
15102
|
+
medium: 2,
|
|
15103
|
+
low: 3
|
|
15104
|
+
};
|
|
15105
|
+
function priorityRank(p) {
|
|
15106
|
+
return PRIORITY_ORDER[p ?? ""] ?? 99;
|
|
15107
|
+
}
|
|
15108
|
+
function createSprintPlanningTools(store) {
|
|
15109
|
+
return [
|
|
15110
|
+
tool7(
|
|
15111
|
+
"gather_sprint_planning_context",
|
|
15112
|
+
"Aggregate all planning-relevant data for proposing the next sprint: approved features, backlog epics, active sprint, velocity reference, blockers, and summary stats",
|
|
15113
|
+
{
|
|
15114
|
+
focusFeature: external_exports.string().optional().describe("Filter backlog to epics of a specific feature ID (e.g. 'F-001')"),
|
|
15115
|
+
sprintDurationDays: external_exports.number().optional().describe("Expected sprint duration in days \u2014 passed through for capacity reasoning")
|
|
15116
|
+
},
|
|
15117
|
+
async (args) => {
|
|
15118
|
+
const features = store.list({ type: "feature" });
|
|
15119
|
+
const epics = store.list({ type: "epic" });
|
|
15120
|
+
const sprints = store.list({ type: "sprint" });
|
|
15121
|
+
const questions = store.list({ type: "question", status: "open" });
|
|
15122
|
+
const contributions = store.list({ type: "contribution" });
|
|
15123
|
+
const approvedFeatures = features.filter((f) => f.frontmatter.status === "approved").sort((a, b) => priorityRank(a.frontmatter.priority) - priorityRank(b.frontmatter.priority)).map((f) => {
|
|
15124
|
+
const linkedEpics = epics.filter((e) => e.frontmatter.linkedFeature === f.frontmatter.id);
|
|
15125
|
+
const epicsByStatus = {};
|
|
15126
|
+
for (const e of linkedEpics) {
|
|
15127
|
+
epicsByStatus[e.frontmatter.status] = (epicsByStatus[e.frontmatter.status] ?? 0) + 1;
|
|
15128
|
+
}
|
|
15129
|
+
return {
|
|
15130
|
+
id: f.frontmatter.id,
|
|
15131
|
+
title: f.frontmatter.title,
|
|
15132
|
+
priority: f.frontmatter.priority,
|
|
15133
|
+
owner: f.frontmatter.owner,
|
|
15134
|
+
epicCount: linkedEpics.length,
|
|
15135
|
+
epicsByStatus
|
|
15136
|
+
};
|
|
15137
|
+
});
|
|
15138
|
+
const assignedEpicIds = /* @__PURE__ */ new Set();
|
|
15139
|
+
for (const sp of sprints) {
|
|
15140
|
+
const linked = sp.frontmatter.linkedEpics ?? [];
|
|
15141
|
+
for (const id of linked) assignedEpicIds.add(id);
|
|
15142
|
+
}
|
|
15143
|
+
const featureMap = new Map(features.map((f) => [f.frontmatter.id, f]));
|
|
15144
|
+
let backlogEpics = epics.filter(
|
|
15145
|
+
(e) => !assignedEpicIds.has(e.frontmatter.id) && e.frontmatter.status !== "done"
|
|
15146
|
+
);
|
|
15147
|
+
if (args.focusFeature) {
|
|
15148
|
+
backlogEpics = backlogEpics.filter(
|
|
15149
|
+
(e) => e.frontmatter.linkedFeature === args.focusFeature
|
|
15150
|
+
);
|
|
15151
|
+
}
|
|
15152
|
+
const backlog = backlogEpics.sort((a, b) => {
|
|
15153
|
+
const fa = featureMap.get(a.frontmatter.linkedFeature);
|
|
15154
|
+
const fb = featureMap.get(b.frontmatter.linkedFeature);
|
|
15155
|
+
return priorityRank(fa?.frontmatter.priority) - priorityRank(fb?.frontmatter.priority);
|
|
15156
|
+
}).map((e) => {
|
|
15157
|
+
const parent = featureMap.get(e.frontmatter.linkedFeature);
|
|
15158
|
+
return {
|
|
15159
|
+
id: e.frontmatter.id,
|
|
15160
|
+
title: e.frontmatter.title,
|
|
15161
|
+
status: e.frontmatter.status,
|
|
15162
|
+
linkedFeature: e.frontmatter.linkedFeature,
|
|
15163
|
+
featureTitle: parent?.frontmatter.title ?? null,
|
|
15164
|
+
featurePriority: parent?.frontmatter.priority ?? null,
|
|
15165
|
+
estimatedEffort: e.frontmatter.estimatedEffort ?? null,
|
|
15166
|
+
targetDate: e.frontmatter.targetDate ?? null
|
|
15167
|
+
};
|
|
15168
|
+
});
|
|
15169
|
+
const activeSprintDoc = sprints.find((s) => s.frontmatter.status === "active") ?? null;
|
|
15170
|
+
let activeSprint = null;
|
|
15171
|
+
if (activeSprintDoc) {
|
|
15172
|
+
const linkedEpicIds = activeSprintDoc.frontmatter.linkedEpics ?? [];
|
|
15173
|
+
const linkedEpics = linkedEpicIds.map((epicId) => {
|
|
15174
|
+
const epic = store.get(epicId);
|
|
15175
|
+
return epic ? { id: epicId, title: epic.frontmatter.title, status: epic.frontmatter.status } : { id: epicId, title: "(not found)", status: "unknown" };
|
|
15176
|
+
});
|
|
15177
|
+
const allDocs = store.list();
|
|
15178
|
+
const sprintTag = `sprint:${activeSprintDoc.frontmatter.id}`;
|
|
15179
|
+
const workItems = allDocs.filter(
|
|
15180
|
+
(d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.tags?.includes(sprintTag)
|
|
15181
|
+
);
|
|
15182
|
+
const doneCount = workItems.filter(
|
|
15183
|
+
(d) => d.frontmatter.status === "done" || d.frontmatter.status === "resolved" || d.frontmatter.status === "closed"
|
|
15184
|
+
).length;
|
|
15185
|
+
const completionPct = workItems.length > 0 ? Math.round(doneCount / workItems.length * 100) : 0;
|
|
15186
|
+
activeSprint = {
|
|
15187
|
+
id: activeSprintDoc.frontmatter.id,
|
|
15188
|
+
title: activeSprintDoc.frontmatter.title,
|
|
15189
|
+
goal: activeSprintDoc.frontmatter.goal,
|
|
15190
|
+
startDate: activeSprintDoc.frontmatter.startDate,
|
|
15191
|
+
endDate: activeSprintDoc.frontmatter.endDate,
|
|
15192
|
+
linkedEpics,
|
|
15193
|
+
workItems: { total: workItems.length, done: doneCount, completionPct }
|
|
15194
|
+
};
|
|
15195
|
+
}
|
|
15196
|
+
const completedSprints = sprints.filter((s) => s.frontmatter.status === "completed").sort((a, b) => {
|
|
15197
|
+
const da = a.frontmatter.endDate ?? "";
|
|
15198
|
+
const db = b.frontmatter.endDate ?? "";
|
|
15199
|
+
return da < db ? 1 : da > db ? -1 : 0;
|
|
15200
|
+
}).slice(0, 2);
|
|
15201
|
+
const velocityReference = completedSprints.map((sp) => {
|
|
15202
|
+
const linkedEpicIds = sp.frontmatter.linkedEpics ?? [];
|
|
15203
|
+
const efforts = [];
|
|
15204
|
+
for (const epicId of linkedEpicIds) {
|
|
15205
|
+
const epic = store.get(epicId);
|
|
15206
|
+
if (epic?.frontmatter.estimatedEffort) {
|
|
15207
|
+
efforts.push(String(epic.frontmatter.estimatedEffort));
|
|
15208
|
+
}
|
|
15209
|
+
}
|
|
15210
|
+
const allDocs = store.list();
|
|
15211
|
+
const sprintTag = `sprint:${sp.frontmatter.id}`;
|
|
15212
|
+
const workItems = allDocs.filter(
|
|
15213
|
+
(d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.tags?.includes(sprintTag)
|
|
15214
|
+
);
|
|
15215
|
+
return {
|
|
15216
|
+
id: sp.frontmatter.id,
|
|
15217
|
+
title: sp.frontmatter.title,
|
|
15218
|
+
startDate: sp.frontmatter.startDate,
|
|
15219
|
+
endDate: sp.frontmatter.endDate,
|
|
15220
|
+
epicCount: linkedEpicIds.length,
|
|
15221
|
+
efforts,
|
|
15222
|
+
workItemCount: workItems.length
|
|
15223
|
+
};
|
|
15224
|
+
});
|
|
15225
|
+
const openBlockerContributions = contributions.filter(
|
|
15226
|
+
(c) => c.frontmatter.status === "open" && (c.frontmatter.contributionType === "risk-finding" || c.frontmatter.contributionType === "blocker-report")
|
|
15227
|
+
);
|
|
15228
|
+
const blockers = {
|
|
15229
|
+
openQuestions: questions.map((q) => ({
|
|
15230
|
+
id: q.frontmatter.id,
|
|
15231
|
+
title: q.frontmatter.title
|
|
15232
|
+
})),
|
|
15233
|
+
openRiskAndBlockerContributions: openBlockerContributions.map((c) => ({
|
|
15234
|
+
id: c.frontmatter.id,
|
|
15235
|
+
title: c.frontmatter.title,
|
|
15236
|
+
contributionType: c.frontmatter.contributionType
|
|
15237
|
+
}))
|
|
15238
|
+
};
|
|
15239
|
+
const totalBacklogEfforts = backlog.filter((e) => e.estimatedEffort !== null).map((e) => String(e.estimatedEffort));
|
|
15240
|
+
const approvedFeaturesWithNoEpics = approvedFeatures.filter((f) => f.epicCount === 0).map((f) => ({ id: f.id, title: f.title }));
|
|
15241
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
15242
|
+
const epicsAtRisk = epics.filter((e) => {
|
|
15243
|
+
if (e.frontmatter.status === "done") return false;
|
|
15244
|
+
if (e.frontmatter.targetDate && e.frontmatter.targetDate < now) return true;
|
|
15245
|
+
const parent = featureMap.get(e.frontmatter.linkedFeature);
|
|
15246
|
+
if (parent?.frontmatter.status === "deferred") return true;
|
|
15247
|
+
return false;
|
|
15248
|
+
}).map((e) => ({
|
|
15249
|
+
id: e.frontmatter.id,
|
|
15250
|
+
title: e.frontmatter.title,
|
|
15251
|
+
reason: e.frontmatter.targetDate && e.frontmatter.targetDate < now ? "past-target-date" : "deferred-feature"
|
|
15252
|
+
}));
|
|
15253
|
+
const plannedSprintCount = sprints.filter((s) => s.frontmatter.status === "planned").length;
|
|
15254
|
+
const summary = {
|
|
15255
|
+
totalBacklogEpics: backlog.length,
|
|
15256
|
+
totalBacklogEfforts,
|
|
15257
|
+
approvedFeaturesWithNoEpics,
|
|
15258
|
+
epicsAtRisk,
|
|
15259
|
+
plannedSprintCount
|
|
15260
|
+
};
|
|
15261
|
+
const result = {
|
|
15262
|
+
approvedFeatures,
|
|
15263
|
+
backlog,
|
|
15264
|
+
activeSprint,
|
|
15265
|
+
velocityReference,
|
|
15266
|
+
blockers,
|
|
15267
|
+
summary,
|
|
15268
|
+
...args.sprintDurationDays !== void 0 ? { sprintDurationDays: args.sprintDurationDays } : {}
|
|
15269
|
+
};
|
|
15270
|
+
return {
|
|
15271
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
15272
|
+
};
|
|
15273
|
+
},
|
|
15274
|
+
{ annotations: { readOnly: true } }
|
|
15275
|
+
)
|
|
15276
|
+
];
|
|
15277
|
+
}
|
|
15278
|
+
|
|
15006
15279
|
// src/plugins/common.ts
|
|
15007
15280
|
var COMMON_REGISTRATIONS = [
|
|
15008
15281
|
{ type: "meeting", dirName: "meetings", idPrefix: "M" },
|
|
@@ -15019,7 +15292,8 @@ function createCommonTools(store) {
|
|
|
15019
15292
|
...createFeatureTools(store),
|
|
15020
15293
|
...createEpicTools(store),
|
|
15021
15294
|
...createContributionTools(store),
|
|
15022
|
-
...createSprintTools(store)
|
|
15295
|
+
...createSprintTools(store),
|
|
15296
|
+
...createSprintPlanningTools(store)
|
|
15023
15297
|
];
|
|
15024
15298
|
}
|
|
15025
15299
|
|
|
@@ -15092,7 +15366,12 @@ var genericAgilePlugin = {
|
|
|
15092
15366
|
- **list_sprints** / **get_sprint**: View sprints to understand iteration scope and delivery dates.
|
|
15093
15367
|
- **update_sprint**: Assign epics to sprints by updating linkedEpics when breaking features into work.
|
|
15094
15368
|
- Tag technical actions and decisions with \`sprint:SP-xxx\` to associate them with a sprint.
|
|
15095
|
-
- Use **generate_sprint_progress** to track technical work completion within an iteration
|
|
15369
|
+
- Use **generate_sprint_progress** to track technical work completion within an iteration.
|
|
15370
|
+
|
|
15371
|
+
**Sprint Planning:**
|
|
15372
|
+
- When asked to plan or propose a sprint, ALWAYS call **gather_sprint_planning_context** first.
|
|
15373
|
+
- Focus on: technical readiness of each epic, open technical questions or spikes, effort balance across the sprint, and feature coverage.
|
|
15374
|
+
- Present a structured sprint proposal with technical rationale for each selected epic, known technical risks, and any prerequisite work that should be completed first.`,
|
|
15096
15375
|
"delivery-manager": `You track delivery across features and epics, manage schedules, and report on progress.
|
|
15097
15376
|
|
|
15098
15377
|
**Report Tools:**
|
|
@@ -15140,7 +15419,13 @@ var genericAgilePlugin = {
|
|
|
15140
15419
|
- Assign epics to sprints via linkedEpics.
|
|
15141
15420
|
- Tag work items (actions, decisions, questions) with \`sprint:SP-xxx\` for sprint scoping.
|
|
15142
15421
|
- Track delivery dates and flag at-risk sprints.
|
|
15143
|
-
- Register past/completed sprints for historical tracking
|
|
15422
|
+
- Register past/completed sprints for historical tracking.
|
|
15423
|
+
|
|
15424
|
+
**Sprint Planning:**
|
|
15425
|
+
- When asked to plan or propose a sprint, ALWAYS call **gather_sprint_planning_context** first. It aggregates approved features, backlog epics, active sprint status, velocity from recent sprints, blockers, and summary stats in one call.
|
|
15426
|
+
- Reason through: priority (critical/high features first), capacity (compare backlog effort to velocity reference), dependencies and blockers, balance across features, and risk.
|
|
15427
|
+
- Present a structured sprint proposal: title, goal, suggested dates, selected epics with rationale for each, excluded epics with reason, and identified risks.
|
|
15428
|
+
- After user confirmation, use **create_sprint** with the agreed epics to persist the sprint.`,
|
|
15144
15429
|
"*": `You have access to feature, epic, sprint, and meeting tools for project coordination:
|
|
15145
15430
|
|
|
15146
15431
|
**Features** (F-xxx): Product capabilities defined by the Product Owner. Features progress through draft \u2192 approved \u2192 done.
|
|
@@ -15163,10 +15448,10 @@ var genericAgilePlugin = {
|
|
|
15163
15448
|
};
|
|
15164
15449
|
|
|
15165
15450
|
// src/plugins/builtin/tools/use-cases.ts
|
|
15166
|
-
import { tool as
|
|
15451
|
+
import { tool as tool8 } from "@anthropic-ai/claude-agent-sdk";
|
|
15167
15452
|
function createUseCaseTools(store) {
|
|
15168
15453
|
return [
|
|
15169
|
-
|
|
15454
|
+
tool8(
|
|
15170
15455
|
"list_use_cases",
|
|
15171
15456
|
"List all extension use cases, optionally filtered by status or extension type",
|
|
15172
15457
|
{
|
|
@@ -15196,7 +15481,7 @@ function createUseCaseTools(store) {
|
|
|
15196
15481
|
},
|
|
15197
15482
|
{ annotations: { readOnly: true } }
|
|
15198
15483
|
),
|
|
15199
|
-
|
|
15484
|
+
tool8(
|
|
15200
15485
|
"get_use_case",
|
|
15201
15486
|
"Get the full content of a specific use case by ID",
|
|
15202
15487
|
{ id: external_exports.string().describe("Use case ID (e.g. 'UC-001')") },
|
|
@@ -15223,7 +15508,7 @@ function createUseCaseTools(store) {
|
|
|
15223
15508
|
},
|
|
15224
15509
|
{ annotations: { readOnly: true } }
|
|
15225
15510
|
),
|
|
15226
|
-
|
|
15511
|
+
tool8(
|
|
15227
15512
|
"create_use_case",
|
|
15228
15513
|
"Create a new extension use case definition (Phase 1: Assess Extension Use Case)",
|
|
15229
15514
|
{
|
|
@@ -15257,7 +15542,7 @@ function createUseCaseTools(store) {
|
|
|
15257
15542
|
};
|
|
15258
15543
|
}
|
|
15259
15544
|
),
|
|
15260
|
-
|
|
15545
|
+
tool8(
|
|
15261
15546
|
"update_use_case",
|
|
15262
15547
|
"Update an existing extension use case",
|
|
15263
15548
|
{
|
|
@@ -15287,10 +15572,10 @@ function createUseCaseTools(store) {
|
|
|
15287
15572
|
}
|
|
15288
15573
|
|
|
15289
15574
|
// src/plugins/builtin/tools/tech-assessments.ts
|
|
15290
|
-
import { tool as
|
|
15575
|
+
import { tool as tool9 } from "@anthropic-ai/claude-agent-sdk";
|
|
15291
15576
|
function createTechAssessmentTools(store) {
|
|
15292
15577
|
return [
|
|
15293
|
-
|
|
15578
|
+
tool9(
|
|
15294
15579
|
"list_tech_assessments",
|
|
15295
15580
|
"List all technology assessments, optionally filtered by status",
|
|
15296
15581
|
{
|
|
@@ -15321,7 +15606,7 @@ function createTechAssessmentTools(store) {
|
|
|
15321
15606
|
},
|
|
15322
15607
|
{ annotations: { readOnly: true } }
|
|
15323
15608
|
),
|
|
15324
|
-
|
|
15609
|
+
tool9(
|
|
15325
15610
|
"get_tech_assessment",
|
|
15326
15611
|
"Get the full content of a specific technology assessment by ID",
|
|
15327
15612
|
{ id: external_exports.string().describe("Tech assessment ID (e.g. 'TA-001')") },
|
|
@@ -15348,7 +15633,7 @@ function createTechAssessmentTools(store) {
|
|
|
15348
15633
|
},
|
|
15349
15634
|
{ annotations: { readOnly: true } }
|
|
15350
15635
|
),
|
|
15351
|
-
|
|
15636
|
+
tool9(
|
|
15352
15637
|
"create_tech_assessment",
|
|
15353
15638
|
"Create a new technology assessment linked to an assessed/approved use case (Phase 2: Assess Extension Technology)",
|
|
15354
15639
|
{
|
|
@@ -15419,7 +15704,7 @@ function createTechAssessmentTools(store) {
|
|
|
15419
15704
|
};
|
|
15420
15705
|
}
|
|
15421
15706
|
),
|
|
15422
|
-
|
|
15707
|
+
tool9(
|
|
15423
15708
|
"update_tech_assessment",
|
|
15424
15709
|
"Update an existing technology assessment. The linked use case cannot be changed.",
|
|
15425
15710
|
{
|
|
@@ -15449,10 +15734,10 @@ function createTechAssessmentTools(store) {
|
|
|
15449
15734
|
}
|
|
15450
15735
|
|
|
15451
15736
|
// src/plugins/builtin/tools/extension-designs.ts
|
|
15452
|
-
import { tool as
|
|
15737
|
+
import { tool as tool10 } from "@anthropic-ai/claude-agent-sdk";
|
|
15453
15738
|
function createExtensionDesignTools(store) {
|
|
15454
15739
|
return [
|
|
15455
|
-
|
|
15740
|
+
tool10(
|
|
15456
15741
|
"list_extension_designs",
|
|
15457
15742
|
"List all extension designs, optionally filtered by status",
|
|
15458
15743
|
{
|
|
@@ -15482,7 +15767,7 @@ function createExtensionDesignTools(store) {
|
|
|
15482
15767
|
},
|
|
15483
15768
|
{ annotations: { readOnly: true } }
|
|
15484
15769
|
),
|
|
15485
|
-
|
|
15770
|
+
tool10(
|
|
15486
15771
|
"get_extension_design",
|
|
15487
15772
|
"Get the full content of a specific extension design by ID",
|
|
15488
15773
|
{ id: external_exports.string().describe("Extension design ID (e.g. 'XD-001')") },
|
|
@@ -15509,7 +15794,7 @@ function createExtensionDesignTools(store) {
|
|
|
15509
15794
|
},
|
|
15510
15795
|
{ annotations: { readOnly: true } }
|
|
15511
15796
|
),
|
|
15512
|
-
|
|
15797
|
+
tool10(
|
|
15513
15798
|
"create_extension_design",
|
|
15514
15799
|
"Create a new extension design linked to a recommended tech assessment (Phase 3: Define Extension Target Solution)",
|
|
15515
15800
|
{
|
|
@@ -15577,7 +15862,7 @@ function createExtensionDesignTools(store) {
|
|
|
15577
15862
|
};
|
|
15578
15863
|
}
|
|
15579
15864
|
),
|
|
15580
|
-
|
|
15865
|
+
tool10(
|
|
15581
15866
|
"update_extension_design",
|
|
15582
15867
|
"Update an existing extension design. The linked tech assessment cannot be changed.",
|
|
15583
15868
|
{
|
|
@@ -15606,10 +15891,10 @@ function createExtensionDesignTools(store) {
|
|
|
15606
15891
|
}
|
|
15607
15892
|
|
|
15608
15893
|
// src/plugins/builtin/tools/aem-reports.ts
|
|
15609
|
-
import { tool as
|
|
15894
|
+
import { tool as tool11 } from "@anthropic-ai/claude-agent-sdk";
|
|
15610
15895
|
function createAemReportTools(store) {
|
|
15611
15896
|
return [
|
|
15612
|
-
|
|
15897
|
+
tool11(
|
|
15613
15898
|
"generate_extension_portfolio",
|
|
15614
15899
|
"Generate a portfolio view of all use cases with their linked tech assessments and extension designs",
|
|
15615
15900
|
{},
|
|
@@ -15661,7 +15946,7 @@ function createAemReportTools(store) {
|
|
|
15661
15946
|
},
|
|
15662
15947
|
{ annotations: { readOnly: true } }
|
|
15663
15948
|
),
|
|
15664
|
-
|
|
15949
|
+
tool11(
|
|
15665
15950
|
"generate_tech_readiness",
|
|
15666
15951
|
"Generate a BTP technology readiness report showing service coverage and gaps across assessments",
|
|
15667
15952
|
{},
|
|
@@ -15713,7 +15998,7 @@ function createAemReportTools(store) {
|
|
|
15713
15998
|
},
|
|
15714
15999
|
{ annotations: { readOnly: true } }
|
|
15715
16000
|
),
|
|
15716
|
-
|
|
16001
|
+
tool11(
|
|
15717
16002
|
"generate_phase_status",
|
|
15718
16003
|
"Generate a phase progress report showing artifact counts and readiness per AEM phase",
|
|
15719
16004
|
{},
|
|
@@ -15775,11 +16060,11 @@ function createAemReportTools(store) {
|
|
|
15775
16060
|
import * as fs3 from "fs";
|
|
15776
16061
|
import * as path3 from "path";
|
|
15777
16062
|
import * as YAML2 from "yaml";
|
|
15778
|
-
import { tool as
|
|
16063
|
+
import { tool as tool12 } from "@anthropic-ai/claude-agent-sdk";
|
|
15779
16064
|
var PHASES = ["assess-use-case", "assess-technology", "define-solution"];
|
|
15780
16065
|
function createAemPhaseTools(store, marvinDir) {
|
|
15781
16066
|
return [
|
|
15782
|
-
|
|
16067
|
+
tool12(
|
|
15783
16068
|
"get_current_phase",
|
|
15784
16069
|
"Get the current AEM phase from project configuration",
|
|
15785
16070
|
{},
|
|
@@ -15800,7 +16085,7 @@ function createAemPhaseTools(store, marvinDir) {
|
|
|
15800
16085
|
},
|
|
15801
16086
|
{ annotations: { readOnly: true } }
|
|
15802
16087
|
),
|
|
15803
|
-
|
|
16088
|
+
tool12(
|
|
15804
16089
|
"advance_phase",
|
|
15805
16090
|
"Advance to the next AEM phase. Performs soft gate checks and warns if artifacts are incomplete, but does not block.",
|
|
15806
16091
|
{
|
|
@@ -16668,10 +16953,10 @@ import {
|
|
|
16668
16953
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
16669
16954
|
|
|
16670
16955
|
// src/agent/tools/decisions.ts
|
|
16671
|
-
import { tool as
|
|
16956
|
+
import { tool as tool13 } from "@anthropic-ai/claude-agent-sdk";
|
|
16672
16957
|
function createDecisionTools(store) {
|
|
16673
16958
|
return [
|
|
16674
|
-
|
|
16959
|
+
tool13(
|
|
16675
16960
|
"list_decisions",
|
|
16676
16961
|
"List all decisions in the project, optionally filtered by status",
|
|
16677
16962
|
{ status: external_exports.string().optional().describe("Filter by status (e.g. 'open', 'decided', 'superseded')") },
|
|
@@ -16689,7 +16974,7 @@ function createDecisionTools(store) {
|
|
|
16689
16974
|
},
|
|
16690
16975
|
{ annotations: { readOnly: true } }
|
|
16691
16976
|
),
|
|
16692
|
-
|
|
16977
|
+
tool13(
|
|
16693
16978
|
"get_decision",
|
|
16694
16979
|
"Get the full content of a specific decision by ID",
|
|
16695
16980
|
{ id: external_exports.string().describe("Decision ID (e.g. 'D-001')") },
|
|
@@ -16716,7 +17001,7 @@ function createDecisionTools(store) {
|
|
|
16716
17001
|
},
|
|
16717
17002
|
{ annotations: { readOnly: true } }
|
|
16718
17003
|
),
|
|
16719
|
-
|
|
17004
|
+
tool13(
|
|
16720
17005
|
"create_decision",
|
|
16721
17006
|
"Create a new decision record",
|
|
16722
17007
|
{
|
|
@@ -16747,7 +17032,7 @@ function createDecisionTools(store) {
|
|
|
16747
17032
|
};
|
|
16748
17033
|
}
|
|
16749
17034
|
),
|
|
16750
|
-
|
|
17035
|
+
tool13(
|
|
16751
17036
|
"update_decision",
|
|
16752
17037
|
"Update an existing decision",
|
|
16753
17038
|
{
|
|
@@ -16774,10 +17059,10 @@ function createDecisionTools(store) {
|
|
|
16774
17059
|
}
|
|
16775
17060
|
|
|
16776
17061
|
// src/agent/tools/actions.ts
|
|
16777
|
-
import { tool as
|
|
17062
|
+
import { tool as tool14 } from "@anthropic-ai/claude-agent-sdk";
|
|
16778
17063
|
function createActionTools(store) {
|
|
16779
17064
|
return [
|
|
16780
|
-
|
|
17065
|
+
tool14(
|
|
16781
17066
|
"list_actions",
|
|
16782
17067
|
"List all action items in the project, optionally filtered by status or owner",
|
|
16783
17068
|
{
|
|
@@ -16804,7 +17089,7 @@ function createActionTools(store) {
|
|
|
16804
17089
|
},
|
|
16805
17090
|
{ annotations: { readOnly: true } }
|
|
16806
17091
|
),
|
|
16807
|
-
|
|
17092
|
+
tool14(
|
|
16808
17093
|
"get_action",
|
|
16809
17094
|
"Get the full content of a specific action item by ID",
|
|
16810
17095
|
{ id: external_exports.string().describe("Action ID (e.g. 'A-001')") },
|
|
@@ -16831,7 +17116,7 @@ function createActionTools(store) {
|
|
|
16831
17116
|
},
|
|
16832
17117
|
{ annotations: { readOnly: true } }
|
|
16833
17118
|
),
|
|
16834
|
-
|
|
17119
|
+
tool14(
|
|
16835
17120
|
"create_action",
|
|
16836
17121
|
"Create a new action item",
|
|
16837
17122
|
{
|
|
@@ -16864,7 +17149,7 @@ function createActionTools(store) {
|
|
|
16864
17149
|
};
|
|
16865
17150
|
}
|
|
16866
17151
|
),
|
|
16867
|
-
|
|
17152
|
+
tool14(
|
|
16868
17153
|
"update_action",
|
|
16869
17154
|
"Update an existing action item",
|
|
16870
17155
|
{
|
|
@@ -16892,10 +17177,10 @@ function createActionTools(store) {
|
|
|
16892
17177
|
}
|
|
16893
17178
|
|
|
16894
17179
|
// src/agent/tools/questions.ts
|
|
16895
|
-
import { tool as
|
|
17180
|
+
import { tool as tool15 } from "@anthropic-ai/claude-agent-sdk";
|
|
16896
17181
|
function createQuestionTools(store) {
|
|
16897
17182
|
return [
|
|
16898
|
-
|
|
17183
|
+
tool15(
|
|
16899
17184
|
"list_questions",
|
|
16900
17185
|
"List all questions in the project, optionally filtered by status",
|
|
16901
17186
|
{
|
|
@@ -16916,7 +17201,7 @@ function createQuestionTools(store) {
|
|
|
16916
17201
|
},
|
|
16917
17202
|
{ annotations: { readOnly: true } }
|
|
16918
17203
|
),
|
|
16919
|
-
|
|
17204
|
+
tool15(
|
|
16920
17205
|
"get_question",
|
|
16921
17206
|
"Get the full content of a specific question by ID",
|
|
16922
17207
|
{ id: external_exports.string().describe("Question ID (e.g. 'Q-001')") },
|
|
@@ -16943,7 +17228,7 @@ function createQuestionTools(store) {
|
|
|
16943
17228
|
},
|
|
16944
17229
|
{ annotations: { readOnly: true } }
|
|
16945
17230
|
),
|
|
16946
|
-
|
|
17231
|
+
tool15(
|
|
16947
17232
|
"create_question",
|
|
16948
17233
|
"Create a new question that needs to be answered",
|
|
16949
17234
|
{
|
|
@@ -16974,7 +17259,7 @@ function createQuestionTools(store) {
|
|
|
16974
17259
|
};
|
|
16975
17260
|
}
|
|
16976
17261
|
),
|
|
16977
|
-
|
|
17262
|
+
tool15(
|
|
16978
17263
|
"update_question",
|
|
16979
17264
|
"Update an existing question",
|
|
16980
17265
|
{
|
|
@@ -17001,10 +17286,10 @@ function createQuestionTools(store) {
|
|
|
17001
17286
|
}
|
|
17002
17287
|
|
|
17003
17288
|
// src/agent/tools/documents.ts
|
|
17004
|
-
import { tool as
|
|
17289
|
+
import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
|
|
17005
17290
|
function createDocumentTools(store) {
|
|
17006
17291
|
return [
|
|
17007
|
-
|
|
17292
|
+
tool16(
|
|
17008
17293
|
"search_documents",
|
|
17009
17294
|
"Search all project documents, optionally filtered by type, status, or tag",
|
|
17010
17295
|
{
|
|
@@ -17036,7 +17321,7 @@ function createDocumentTools(store) {
|
|
|
17036
17321
|
},
|
|
17037
17322
|
{ annotations: { readOnly: true } }
|
|
17038
17323
|
),
|
|
17039
|
-
|
|
17324
|
+
tool16(
|
|
17040
17325
|
"read_document",
|
|
17041
17326
|
"Read the full content of any project document by ID",
|
|
17042
17327
|
{ id: external_exports.string().describe("Document ID (e.g. 'D-001', 'A-003', 'Q-002')") },
|
|
@@ -17063,7 +17348,7 @@ function createDocumentTools(store) {
|
|
|
17063
17348
|
},
|
|
17064
17349
|
{ annotations: { readOnly: true } }
|
|
17065
17350
|
),
|
|
17066
|
-
|
|
17351
|
+
tool16(
|
|
17067
17352
|
"project_summary",
|
|
17068
17353
|
"Get a summary of all project documents and their counts",
|
|
17069
17354
|
{},
|
|
@@ -17095,10 +17380,10 @@ function createDocumentTools(store) {
|
|
|
17095
17380
|
}
|
|
17096
17381
|
|
|
17097
17382
|
// src/agent/tools/sources.ts
|
|
17098
|
-
import { tool as
|
|
17383
|
+
import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
|
|
17099
17384
|
function createSourceTools(manifest) {
|
|
17100
17385
|
return [
|
|
17101
|
-
|
|
17386
|
+
tool17(
|
|
17102
17387
|
"list_sources",
|
|
17103
17388
|
"List all source documents and their processing status",
|
|
17104
17389
|
{
|
|
@@ -17128,7 +17413,7 @@ function createSourceTools(manifest) {
|
|
|
17128
17413
|
},
|
|
17129
17414
|
{ annotations: { readOnly: true } }
|
|
17130
17415
|
),
|
|
17131
|
-
|
|
17416
|
+
tool17(
|
|
17132
17417
|
"get_source_info",
|
|
17133
17418
|
"Get detailed information about a specific source document",
|
|
17134
17419
|
{
|
|
@@ -17166,10 +17451,10 @@ function createSourceTools(manifest) {
|
|
|
17166
17451
|
}
|
|
17167
17452
|
|
|
17168
17453
|
// src/agent/tools/sessions.ts
|
|
17169
|
-
import { tool as
|
|
17454
|
+
import { tool as tool18 } from "@anthropic-ai/claude-agent-sdk";
|
|
17170
17455
|
function createSessionTools(store) {
|
|
17171
17456
|
return [
|
|
17172
|
-
|
|
17457
|
+
tool18(
|
|
17173
17458
|
"list_sessions",
|
|
17174
17459
|
"List all saved chat sessions, sorted by most recently used",
|
|
17175
17460
|
{
|
|
@@ -17193,7 +17478,7 @@ function createSessionTools(store) {
|
|
|
17193
17478
|
},
|
|
17194
17479
|
{ annotations: { readOnly: true } }
|
|
17195
17480
|
),
|
|
17196
|
-
|
|
17481
|
+
tool18(
|
|
17197
17482
|
"get_session",
|
|
17198
17483
|
"Get details of a specific saved session by name",
|
|
17199
17484
|
{ name: external_exports.string().describe("Session name (e.g. 'jwt-auth-decision')") },
|
|
@@ -17421,9 +17706,495 @@ Be thorough but concise. Focus on actionable insights.`,
|
|
|
17421
17706
|
]
|
|
17422
17707
|
};
|
|
17423
17708
|
|
|
17709
|
+
// src/skills/builtin/jira/tools.ts
|
|
17710
|
+
import { tool as tool19 } from "@anthropic-ai/claude-agent-sdk";
|
|
17711
|
+
|
|
17712
|
+
// src/skills/builtin/jira/client.ts
|
|
17713
|
+
var JiraClient = class {
|
|
17714
|
+
baseUrl;
|
|
17715
|
+
authHeader;
|
|
17716
|
+
constructor(config2) {
|
|
17717
|
+
this.baseUrl = `https://${config2.host}/rest/api/2`;
|
|
17718
|
+
this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
|
|
17719
|
+
}
|
|
17720
|
+
async request(path18, method = "GET", body) {
|
|
17721
|
+
const url2 = `${this.baseUrl}${path18}`;
|
|
17722
|
+
const headers = {
|
|
17723
|
+
Authorization: this.authHeader,
|
|
17724
|
+
"Content-Type": "application/json",
|
|
17725
|
+
Accept: "application/json"
|
|
17726
|
+
};
|
|
17727
|
+
const response = await fetch(url2, {
|
|
17728
|
+
method,
|
|
17729
|
+
headers,
|
|
17730
|
+
body: body ? JSON.stringify(body) : void 0
|
|
17731
|
+
});
|
|
17732
|
+
if (!response.ok) {
|
|
17733
|
+
const text = await response.text().catch(() => "");
|
|
17734
|
+
throw new Error(
|
|
17735
|
+
`Jira API error ${response.status} ${method} ${path18}: ${text}`
|
|
17736
|
+
);
|
|
17737
|
+
}
|
|
17738
|
+
if (response.status === 204) return void 0;
|
|
17739
|
+
return response.json();
|
|
17740
|
+
}
|
|
17741
|
+
async searchIssues(jql, maxResults = 50) {
|
|
17742
|
+
const params = new URLSearchParams({
|
|
17743
|
+
jql,
|
|
17744
|
+
maxResults: String(maxResults)
|
|
17745
|
+
});
|
|
17746
|
+
return this.request(`/search?${params}`);
|
|
17747
|
+
}
|
|
17748
|
+
async getIssue(key) {
|
|
17749
|
+
return this.request(`/issue/${encodeURIComponent(key)}`);
|
|
17750
|
+
}
|
|
17751
|
+
async createIssue(fields) {
|
|
17752
|
+
return this.request("/issue", "POST", { fields });
|
|
17753
|
+
}
|
|
17754
|
+
async updateIssue(key, fields) {
|
|
17755
|
+
await this.request(
|
|
17756
|
+
`/issue/${encodeURIComponent(key)}`,
|
|
17757
|
+
"PUT",
|
|
17758
|
+
{ fields }
|
|
17759
|
+
);
|
|
17760
|
+
}
|
|
17761
|
+
async addComment(key, body) {
|
|
17762
|
+
await this.request(
|
|
17763
|
+
`/issue/${encodeURIComponent(key)}/comment`,
|
|
17764
|
+
"POST",
|
|
17765
|
+
{ body }
|
|
17766
|
+
);
|
|
17767
|
+
}
|
|
17768
|
+
};
|
|
17769
|
+
function createJiraClient() {
|
|
17770
|
+
const host = process.env.JIRA_HOST;
|
|
17771
|
+
const email3 = process.env.JIRA_EMAIL;
|
|
17772
|
+
const apiToken = process.env.JIRA_API_TOKEN;
|
|
17773
|
+
if (!host || !email3 || !apiToken) return null;
|
|
17774
|
+
return new JiraClient({ host, email: email3, apiToken });
|
|
17775
|
+
}
|
|
17776
|
+
|
|
17777
|
+
// src/skills/builtin/jira/tools.ts
|
|
17778
|
+
var JIRA_TYPE = "jira-issue";
|
|
17779
|
+
function jiraNotConfiguredError() {
|
|
17780
|
+
return {
|
|
17781
|
+
content: [
|
|
17782
|
+
{
|
|
17783
|
+
type: "text",
|
|
17784
|
+
text: "Jira is not configured. Set JIRA_HOST, JIRA_EMAIL, and JIRA_API_TOKEN environment variables."
|
|
17785
|
+
}
|
|
17786
|
+
],
|
|
17787
|
+
isError: true
|
|
17788
|
+
};
|
|
17789
|
+
}
|
|
17790
|
+
function mapJiraStatus(jiraStatus) {
|
|
17791
|
+
const lower = jiraStatus.toLowerCase();
|
|
17792
|
+
if (lower === "done" || lower === "closed" || lower === "resolved") return "done";
|
|
17793
|
+
if (lower === "in progress" || lower === "in review") return "in-progress";
|
|
17794
|
+
return "open";
|
|
17795
|
+
}
|
|
17796
|
+
function jiraIssueToFrontmatter(issue2, host, linkedArtifacts) {
|
|
17797
|
+
return {
|
|
17798
|
+
title: issue2.fields.summary,
|
|
17799
|
+
status: mapJiraStatus(issue2.fields.status.name),
|
|
17800
|
+
jiraKey: issue2.key,
|
|
17801
|
+
jiraUrl: `https://${host}/browse/${issue2.key}`,
|
|
17802
|
+
issueType: issue2.fields.issuetype.name,
|
|
17803
|
+
priority: issue2.fields.priority?.name ?? "None",
|
|
17804
|
+
assignee: issue2.fields.assignee?.displayName ?? "",
|
|
17805
|
+
labels: issue2.fields.labels ?? [],
|
|
17806
|
+
linkedArtifacts: linkedArtifacts ?? [],
|
|
17807
|
+
tags: [`jira:${issue2.key}`],
|
|
17808
|
+
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17809
|
+
};
|
|
17810
|
+
}
|
|
17811
|
+
function findByJiraKey(store, jiraKey) {
|
|
17812
|
+
const docs = store.list({ type: JIRA_TYPE });
|
|
17813
|
+
return docs.find((d) => d.frontmatter.jiraKey === jiraKey);
|
|
17814
|
+
}
|
|
17815
|
+
function createJiraTools(store) {
|
|
17816
|
+
return [
|
|
17817
|
+
// --- Local read tools ---
|
|
17818
|
+
tool19(
|
|
17819
|
+
"list_jira_issues",
|
|
17820
|
+
"List locally synced Jira issues (JI-xxx documents), optionally filtered by status or Jira key",
|
|
17821
|
+
{
|
|
17822
|
+
status: external_exports.enum(["open", "in-progress", "done"]).optional().describe("Filter by local status"),
|
|
17823
|
+
jiraKey: external_exports.string().optional().describe("Filter by Jira issue key (e.g. 'PROJ-123')")
|
|
17824
|
+
},
|
|
17825
|
+
async (args) => {
|
|
17826
|
+
let docs = store.list({ type: JIRA_TYPE, status: args.status });
|
|
17827
|
+
if (args.jiraKey) {
|
|
17828
|
+
docs = docs.filter((d) => d.frontmatter.jiraKey === args.jiraKey);
|
|
17829
|
+
}
|
|
17830
|
+
const summary = docs.map((d) => ({
|
|
17831
|
+
id: d.frontmatter.id,
|
|
17832
|
+
title: d.frontmatter.title,
|
|
17833
|
+
status: d.frontmatter.status,
|
|
17834
|
+
jiraKey: d.frontmatter.jiraKey,
|
|
17835
|
+
issueType: d.frontmatter.issueType,
|
|
17836
|
+
priority: d.frontmatter.priority,
|
|
17837
|
+
assignee: d.frontmatter.assignee,
|
|
17838
|
+
linkedArtifacts: d.frontmatter.linkedArtifacts
|
|
17839
|
+
}));
|
|
17840
|
+
return {
|
|
17841
|
+
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
17842
|
+
};
|
|
17843
|
+
},
|
|
17844
|
+
{ annotations: { readOnly: true } }
|
|
17845
|
+
),
|
|
17846
|
+
tool19(
|
|
17847
|
+
"get_jira_issue",
|
|
17848
|
+
"Get the full content of a locally synced Jira issue by local ID (JI-xxx) or Jira key (PROJ-123)",
|
|
17849
|
+
{
|
|
17850
|
+
id: external_exports.string().describe("Local ID (e.g. 'JI-001') or Jira key (e.g. 'PROJ-123')")
|
|
17851
|
+
},
|
|
17852
|
+
async (args) => {
|
|
17853
|
+
let doc = store.get(args.id);
|
|
17854
|
+
if (!doc) {
|
|
17855
|
+
doc = findByJiraKey(store, args.id);
|
|
17856
|
+
}
|
|
17857
|
+
if (!doc) {
|
|
17858
|
+
return {
|
|
17859
|
+
content: [{ type: "text", text: `Jira issue ${args.id} not found locally` }],
|
|
17860
|
+
isError: true
|
|
17861
|
+
};
|
|
17862
|
+
}
|
|
17863
|
+
return {
|
|
17864
|
+
content: [
|
|
17865
|
+
{
|
|
17866
|
+
type: "text",
|
|
17867
|
+
text: JSON.stringify(
|
|
17868
|
+
{ ...doc.frontmatter, content: doc.content },
|
|
17869
|
+
null,
|
|
17870
|
+
2
|
|
17871
|
+
)
|
|
17872
|
+
}
|
|
17873
|
+
]
|
|
17874
|
+
};
|
|
17875
|
+
},
|
|
17876
|
+
{ annotations: { readOnly: true } }
|
|
17877
|
+
),
|
|
17878
|
+
// --- Jira → Local tools ---
|
|
17879
|
+
tool19(
|
|
17880
|
+
"pull_jira_issue",
|
|
17881
|
+
"Fetch a single Jira issue by key and create/update a local JI-xxx document",
|
|
17882
|
+
{
|
|
17883
|
+
key: external_exports.string().describe("Jira issue key (e.g. 'PROJ-123')")
|
|
17884
|
+
},
|
|
17885
|
+
async (args) => {
|
|
17886
|
+
const client = createJiraClient();
|
|
17887
|
+
if (!client) return jiraNotConfiguredError();
|
|
17888
|
+
const issue2 = await client.getIssue(args.key);
|
|
17889
|
+
const host = process.env.JIRA_HOST;
|
|
17890
|
+
const existing = findByJiraKey(store, args.key);
|
|
17891
|
+
if (existing) {
|
|
17892
|
+
const fm2 = jiraIssueToFrontmatter(
|
|
17893
|
+
issue2,
|
|
17894
|
+
host,
|
|
17895
|
+
existing.frontmatter.linkedArtifacts
|
|
17896
|
+
);
|
|
17897
|
+
const doc2 = store.update(
|
|
17898
|
+
existing.frontmatter.id,
|
|
17899
|
+
fm2,
|
|
17900
|
+
issue2.fields.description ?? ""
|
|
17901
|
+
);
|
|
17902
|
+
return {
|
|
17903
|
+
content: [
|
|
17904
|
+
{
|
|
17905
|
+
type: "text",
|
|
17906
|
+
text: `Updated ${doc2.frontmatter.id} from Jira ${args.key}`
|
|
17907
|
+
}
|
|
17908
|
+
]
|
|
17909
|
+
};
|
|
17910
|
+
}
|
|
17911
|
+
const fm = jiraIssueToFrontmatter(issue2, host);
|
|
17912
|
+
const doc = store.create(
|
|
17913
|
+
JIRA_TYPE,
|
|
17914
|
+
fm,
|
|
17915
|
+
issue2.fields.description ?? ""
|
|
17916
|
+
);
|
|
17917
|
+
return {
|
|
17918
|
+
content: [
|
|
17919
|
+
{
|
|
17920
|
+
type: "text",
|
|
17921
|
+
text: `Created ${doc.frontmatter.id} from Jira ${args.key}`
|
|
17922
|
+
}
|
|
17923
|
+
]
|
|
17924
|
+
};
|
|
17925
|
+
}
|
|
17926
|
+
),
|
|
17927
|
+
tool19(
|
|
17928
|
+
"pull_jira_issues_jql",
|
|
17929
|
+
"Bulk fetch Jira issues via JQL query and create/update local JI-xxx documents",
|
|
17930
|
+
{
|
|
17931
|
+
jql: external_exports.string().describe(`JQL query (e.g. 'project = PROJ AND status = "In Progress"')`),
|
|
17932
|
+
maxResults: external_exports.number().optional().describe("Max issues to fetch (default 50)")
|
|
17933
|
+
},
|
|
17934
|
+
async (args) => {
|
|
17935
|
+
const client = createJiraClient();
|
|
17936
|
+
if (!client) return jiraNotConfiguredError();
|
|
17937
|
+
const result = await client.searchIssues(args.jql, args.maxResults);
|
|
17938
|
+
const host = process.env.JIRA_HOST;
|
|
17939
|
+
const created = [];
|
|
17940
|
+
const updated = [];
|
|
17941
|
+
for (const issue2 of result.issues) {
|
|
17942
|
+
const existing = findByJiraKey(store, issue2.key);
|
|
17943
|
+
if (existing) {
|
|
17944
|
+
const fm = jiraIssueToFrontmatter(
|
|
17945
|
+
issue2,
|
|
17946
|
+
host,
|
|
17947
|
+
existing.frontmatter.linkedArtifacts
|
|
17948
|
+
);
|
|
17949
|
+
store.update(
|
|
17950
|
+
existing.frontmatter.id,
|
|
17951
|
+
fm,
|
|
17952
|
+
issue2.fields.description ?? ""
|
|
17953
|
+
);
|
|
17954
|
+
updated.push(`${existing.frontmatter.id} (${issue2.key})`);
|
|
17955
|
+
} else {
|
|
17956
|
+
const fm = jiraIssueToFrontmatter(issue2, host);
|
|
17957
|
+
const doc = store.create(
|
|
17958
|
+
JIRA_TYPE,
|
|
17959
|
+
fm,
|
|
17960
|
+
issue2.fields.description ?? ""
|
|
17961
|
+
);
|
|
17962
|
+
created.push(`${doc.frontmatter.id} (${issue2.key})`);
|
|
17963
|
+
}
|
|
17964
|
+
}
|
|
17965
|
+
const parts = [
|
|
17966
|
+
`Fetched ${result.issues.length} of ${result.total} matching issues.`
|
|
17967
|
+
];
|
|
17968
|
+
if (created.length > 0) parts.push(`Created: ${created.join(", ")}`);
|
|
17969
|
+
if (updated.length > 0) parts.push(`Updated: ${updated.join(", ")}`);
|
|
17970
|
+
return {
|
|
17971
|
+
content: [{ type: "text", text: parts.join("\n") }]
|
|
17972
|
+
};
|
|
17973
|
+
}
|
|
17974
|
+
),
|
|
17975
|
+
// --- Local → Jira tools ---
|
|
17976
|
+
tool19(
|
|
17977
|
+
"push_artifact_to_jira",
|
|
17978
|
+
"Create a Jira issue from any Marvin artifact (D/A/Q/F/E) and create a tracking JI-xxx document",
|
|
17979
|
+
{
|
|
17980
|
+
artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'D-001', 'F-003', 'E-002')"),
|
|
17981
|
+
projectKey: external_exports.string().describe("Jira project key (e.g. 'PROJ')"),
|
|
17982
|
+
issueType: external_exports.enum(["Story", "Task", "Bug", "Epic"]).optional().describe("Jira issue type (default: 'Task')")
|
|
17983
|
+
},
|
|
17984
|
+
async (args) => {
|
|
17985
|
+
const client = createJiraClient();
|
|
17986
|
+
if (!client) return jiraNotConfiguredError();
|
|
17987
|
+
const artifact = store.get(args.artifactId);
|
|
17988
|
+
if (!artifact) {
|
|
17989
|
+
return {
|
|
17990
|
+
content: [
|
|
17991
|
+
{ type: "text", text: `Artifact ${args.artifactId} not found` }
|
|
17992
|
+
],
|
|
17993
|
+
isError: true
|
|
17994
|
+
};
|
|
17995
|
+
}
|
|
17996
|
+
const description = [
|
|
17997
|
+
artifact.content,
|
|
17998
|
+
"",
|
|
17999
|
+
`---`,
|
|
18000
|
+
`Marvin artifact: ${artifact.frontmatter.id} (${artifact.frontmatter.type})`,
|
|
18001
|
+
`Status: ${artifact.frontmatter.status}`
|
|
18002
|
+
].join("\n");
|
|
18003
|
+
const jiraResult = await client.createIssue({
|
|
18004
|
+
project: { key: args.projectKey },
|
|
18005
|
+
summary: artifact.frontmatter.title,
|
|
18006
|
+
description,
|
|
18007
|
+
issuetype: { name: args.issueType ?? "Task" }
|
|
18008
|
+
});
|
|
18009
|
+
const host = process.env.JIRA_HOST;
|
|
18010
|
+
const jiDoc = store.create(
|
|
18011
|
+
JIRA_TYPE,
|
|
18012
|
+
{
|
|
18013
|
+
title: artifact.frontmatter.title,
|
|
18014
|
+
status: "open",
|
|
18015
|
+
jiraKey: jiraResult.key,
|
|
18016
|
+
jiraUrl: `https://${host}/browse/${jiraResult.key}`,
|
|
18017
|
+
issueType: args.issueType ?? "Task",
|
|
18018
|
+
priority: "Medium",
|
|
18019
|
+
assignee: "",
|
|
18020
|
+
labels: [],
|
|
18021
|
+
linkedArtifacts: [args.artifactId],
|
|
18022
|
+
tags: [`jira:${jiraResult.key}`],
|
|
18023
|
+
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18024
|
+
},
|
|
18025
|
+
""
|
|
18026
|
+
);
|
|
18027
|
+
return {
|
|
18028
|
+
content: [
|
|
18029
|
+
{
|
|
18030
|
+
type: "text",
|
|
18031
|
+
text: `Created Jira ${jiraResult.key} from ${args.artifactId}. Tracking locally as ${jiDoc.frontmatter.id}.`
|
|
18032
|
+
}
|
|
18033
|
+
]
|
|
18034
|
+
};
|
|
18035
|
+
}
|
|
18036
|
+
),
|
|
18037
|
+
// --- Bidirectional sync ---
|
|
18038
|
+
tool19(
|
|
18039
|
+
"sync_jira_issue",
|
|
18040
|
+
"Bidirectional sync: push local title/description to Jira, pull latest status/assignee/labels back",
|
|
18041
|
+
{
|
|
18042
|
+
id: external_exports.string().describe("Local JI-xxx ID")
|
|
18043
|
+
},
|
|
18044
|
+
async (args) => {
|
|
18045
|
+
const client = createJiraClient();
|
|
18046
|
+
if (!client) return jiraNotConfiguredError();
|
|
18047
|
+
const doc = store.get(args.id);
|
|
18048
|
+
if (!doc || doc.frontmatter.type !== JIRA_TYPE) {
|
|
18049
|
+
return {
|
|
18050
|
+
content: [
|
|
18051
|
+
{ type: "text", text: `Jira issue ${args.id} not found locally` }
|
|
18052
|
+
],
|
|
18053
|
+
isError: true
|
|
18054
|
+
};
|
|
18055
|
+
}
|
|
18056
|
+
const jiraKey = doc.frontmatter.jiraKey;
|
|
18057
|
+
await client.updateIssue(jiraKey, {
|
|
18058
|
+
summary: doc.frontmatter.title,
|
|
18059
|
+
description: doc.content || void 0
|
|
18060
|
+
});
|
|
18061
|
+
const issue2 = await client.getIssue(jiraKey);
|
|
18062
|
+
const host = process.env.JIRA_HOST;
|
|
18063
|
+
const fm = jiraIssueToFrontmatter(
|
|
18064
|
+
issue2,
|
|
18065
|
+
host,
|
|
18066
|
+
doc.frontmatter.linkedArtifacts
|
|
18067
|
+
);
|
|
18068
|
+
store.update(args.id, fm, issue2.fields.description ?? "");
|
|
18069
|
+
return {
|
|
18070
|
+
content: [
|
|
18071
|
+
{
|
|
18072
|
+
type: "text",
|
|
18073
|
+
text: `Synced ${args.id} \u2194 ${jiraKey}. Status: ${fm.status}, Assignee: ${fm.assignee || "unassigned"}`
|
|
18074
|
+
}
|
|
18075
|
+
]
|
|
18076
|
+
};
|
|
18077
|
+
}
|
|
18078
|
+
),
|
|
18079
|
+
// --- Local link tool ---
|
|
18080
|
+
tool19(
|
|
18081
|
+
"link_artifact_to_jira",
|
|
18082
|
+
"Add a Marvin artifact ID to a JI-xxx document's linkedArtifacts field",
|
|
18083
|
+
{
|
|
18084
|
+
jiraIssueId: external_exports.string().describe("Local JI-xxx ID"),
|
|
18085
|
+
artifactId: external_exports.string().describe("Marvin artifact ID to link (e.g. 'D-001', 'F-003')")
|
|
18086
|
+
},
|
|
18087
|
+
async (args) => {
|
|
18088
|
+
const doc = store.get(args.jiraIssueId);
|
|
18089
|
+
if (!doc || doc.frontmatter.type !== JIRA_TYPE) {
|
|
18090
|
+
return {
|
|
18091
|
+
content: [
|
|
18092
|
+
{
|
|
18093
|
+
type: "text",
|
|
18094
|
+
text: `Jira issue ${args.jiraIssueId} not found locally`
|
|
18095
|
+
}
|
|
18096
|
+
],
|
|
18097
|
+
isError: true
|
|
18098
|
+
};
|
|
18099
|
+
}
|
|
18100
|
+
const artifact = store.get(args.artifactId);
|
|
18101
|
+
if (!artifact) {
|
|
18102
|
+
return {
|
|
18103
|
+
content: [
|
|
18104
|
+
{ type: "text", text: `Artifact ${args.artifactId} not found` }
|
|
18105
|
+
],
|
|
18106
|
+
isError: true
|
|
18107
|
+
};
|
|
18108
|
+
}
|
|
18109
|
+
const linked = doc.frontmatter.linkedArtifacts ?? [];
|
|
18110
|
+
if (linked.includes(args.artifactId)) {
|
|
18111
|
+
return {
|
|
18112
|
+
content: [
|
|
18113
|
+
{
|
|
18114
|
+
type: "text",
|
|
18115
|
+
text: `${args.artifactId} is already linked to ${args.jiraIssueId}`
|
|
18116
|
+
}
|
|
18117
|
+
]
|
|
18118
|
+
};
|
|
18119
|
+
}
|
|
18120
|
+
store.update(args.jiraIssueId, {
|
|
18121
|
+
linkedArtifacts: [...linked, args.artifactId]
|
|
18122
|
+
});
|
|
18123
|
+
return {
|
|
18124
|
+
content: [
|
|
18125
|
+
{
|
|
18126
|
+
type: "text",
|
|
18127
|
+
text: `Linked ${args.artifactId} to ${args.jiraIssueId}`
|
|
18128
|
+
}
|
|
18129
|
+
]
|
|
18130
|
+
};
|
|
18131
|
+
}
|
|
18132
|
+
)
|
|
18133
|
+
];
|
|
18134
|
+
}
|
|
18135
|
+
|
|
18136
|
+
// src/skills/builtin/jira/index.ts
|
|
18137
|
+
var jiraSkill = {
|
|
18138
|
+
id: "jira",
|
|
18139
|
+
name: "Jira Integration",
|
|
18140
|
+
description: "Bidirectional sync between Marvin artifacts and Jira issues",
|
|
18141
|
+
version: "1.0.0",
|
|
18142
|
+
format: "builtin-ts",
|
|
18143
|
+
// No default persona affinity — opt-in via config.yaml skills section
|
|
18144
|
+
documentTypeRegistrations: [
|
|
18145
|
+
{ type: "jira-issue", dirName: "jira-issues", idPrefix: "JI" }
|
|
18146
|
+
],
|
|
18147
|
+
tools: (store) => createJiraTools(store),
|
|
18148
|
+
promptFragments: {
|
|
18149
|
+
"product-owner": `You have the **Jira Integration** skill. You can pull issues from Jira and push Marvin artifacts to Jira.
|
|
18150
|
+
|
|
18151
|
+
**Available tools:**
|
|
18152
|
+
- \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues
|
|
18153
|
+
- \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
|
|
18154
|
+
- \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, feature, etc.)
|
|
18155
|
+
- \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
|
|
18156
|
+
- \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx
|
|
18157
|
+
|
|
18158
|
+
**As Product Owner, use Jira integration to:**
|
|
18159
|
+
- Pull stakeholder-reported issues for triage and prioritization
|
|
18160
|
+
- Push approved features as Stories for development tracking
|
|
18161
|
+
- Link decisions to Jira issues for audit trail and traceability
|
|
18162
|
+
- Use JQL queries to review backlog status (e.g. \`project = PROJ AND status = "To Do"\`)`,
|
|
18163
|
+
"tech-lead": `You have the **Jira Integration** skill. You can pull issues from Jira and push Marvin artifacts to Jira.
|
|
18164
|
+
|
|
18165
|
+
**Available tools:**
|
|
18166
|
+
- \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues
|
|
18167
|
+
- \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
|
|
18168
|
+
- \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, action, epic, etc.)
|
|
18169
|
+
- \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
|
|
18170
|
+
- \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx
|
|
18171
|
+
|
|
18172
|
+
**As Tech Lead, use Jira integration to:**
|
|
18173
|
+
- Pull technical issues and bugs for sprint planning and estimation
|
|
18174
|
+
- Push epics and technical decisions to Jira for cross-team visibility
|
|
18175
|
+
- Bidirectional sync to keep local governance and Jira in alignment
|
|
18176
|
+
- Use JQL queries to track technical debt (e.g. \`labels = "tech-debt" AND status != "Done"\`)`,
|
|
18177
|
+
"delivery-manager": `You have the **Jira Integration** skill. You can pull issues from Jira and push Marvin artifacts to Jira.
|
|
18178
|
+
|
|
18179
|
+
**Available tools:**
|
|
18180
|
+
- \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues
|
|
18181
|
+
- \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
|
|
18182
|
+
- \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, action, etc.)
|
|
18183
|
+
- \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
|
|
18184
|
+
- \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx
|
|
18185
|
+
|
|
18186
|
+
**As Delivery Manager, use Jira integration to:**
|
|
18187
|
+
- Pull sprint issues for tracking progress and blockers
|
|
18188
|
+
- Push actions and decisions to Jira for stakeholder visibility
|
|
18189
|
+
- Use JQL queries for reporting (e.g. \`sprint in openSprints() AND assignee = currentUser()\`)
|
|
18190
|
+
- Sync status between Marvin governance items and Jira issues`
|
|
18191
|
+
}
|
|
18192
|
+
};
|
|
18193
|
+
|
|
17424
18194
|
// src/skills/registry.ts
|
|
17425
18195
|
var BUILTIN_SKILLS = {
|
|
17426
|
-
"governance-review": governanceReviewSkill
|
|
18196
|
+
"governance-review": governanceReviewSkill,
|
|
18197
|
+
"jira": jiraSkill
|
|
17427
18198
|
};
|
|
17428
18199
|
var GOVERNANCE_TOOL_NAMES = [
|
|
17429
18200
|
"mcp__marvin-governance__list_decisions",
|
|
@@ -17572,6 +18343,16 @@ function resolveSkillsForPersona(personaId, skillsConfig, allSkills) {
|
|
|
17572
18343
|
}
|
|
17573
18344
|
return result;
|
|
17574
18345
|
}
|
|
18346
|
+
function collectSkillRegistrations(skillIds, allSkills) {
|
|
18347
|
+
const registrations = [];
|
|
18348
|
+
for (const id of skillIds) {
|
|
18349
|
+
const skill = allSkills.get(id);
|
|
18350
|
+
if (skill?.documentTypeRegistrations) {
|
|
18351
|
+
registrations.push(...skill.documentTypeRegistrations);
|
|
18352
|
+
}
|
|
18353
|
+
}
|
|
18354
|
+
return registrations;
|
|
18355
|
+
}
|
|
17575
18356
|
function getSkillTools(skillIds, allSkills, store) {
|
|
17576
18357
|
const tools = [];
|
|
17577
18358
|
for (const id of skillIds) {
|
|
@@ -17683,16 +18464,17 @@ ${wildcardPrompt}
|
|
|
17683
18464
|
async function startSession(options) {
|
|
17684
18465
|
const { persona, config: config2, marvinDir, projectRoot } = options;
|
|
17685
18466
|
const plugin = resolvePlugin(config2.project.methodology);
|
|
17686
|
-
const
|
|
17687
|
-
const
|
|
18467
|
+
const pluginRegistrations = plugin?.documentTypeRegistrations ?? [];
|
|
18468
|
+
const allSkills = loadAllSkills(marvinDir);
|
|
18469
|
+
const skillIds = resolveSkillsForPersona(persona.id, config2.project.skills, allSkills);
|
|
18470
|
+
const skillRegistrations = collectSkillRegistrations(skillIds, allSkills);
|
|
18471
|
+
const store = new DocumentStore(marvinDir, [...pluginRegistrations, ...skillRegistrations]);
|
|
17688
18472
|
const sessionStore = new SessionStore(marvinDir);
|
|
17689
18473
|
const sourcesDir = path9.join(marvinDir, "sources");
|
|
17690
18474
|
const hasSourcesDir = fs9.existsSync(sourcesDir);
|
|
17691
18475
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
17692
18476
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
17693
18477
|
const pluginPromptFragment = plugin ? getPluginPromptFragment(plugin, persona.id) : void 0;
|
|
17694
|
-
const allSkills = loadAllSkills(marvinDir);
|
|
17695
|
-
const skillIds = resolveSkillsForPersona(persona.id, config2.project.skills, allSkills);
|
|
17696
18478
|
const codeSkillTools = getSkillTools(skillIds, allSkills, store);
|
|
17697
18479
|
const skillAgents = getSkillAgentDefinitions(skillIds, allSkills);
|
|
17698
18480
|
const skillPromptFragment = getSkillPromptFragment(skillIds, allSkills, persona.id);
|
|
@@ -18782,7 +19564,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
18782
19564
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18783
19565
|
|
|
18784
19566
|
// src/skills/action-tools.ts
|
|
18785
|
-
import { tool as
|
|
19567
|
+
import { tool as tool20 } from "@anthropic-ai/claude-agent-sdk";
|
|
18786
19568
|
|
|
18787
19569
|
// src/skills/action-runner.ts
|
|
18788
19570
|
import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -18848,7 +19630,7 @@ function createSkillActionTools(skills, context) {
|
|
|
18848
19630
|
if (!skill.actions) continue;
|
|
18849
19631
|
for (const action of skill.actions) {
|
|
18850
19632
|
tools.push(
|
|
18851
|
-
|
|
19633
|
+
tool20(
|
|
18852
19634
|
`${skill.id}__${action.id}`,
|
|
18853
19635
|
action.description,
|
|
18854
19636
|
{
|
|
@@ -18940,10 +19722,10 @@ ${lines.join("\n\n")}`;
|
|
|
18940
19722
|
}
|
|
18941
19723
|
|
|
18942
19724
|
// src/mcp/persona-tools.ts
|
|
18943
|
-
import { tool as
|
|
19725
|
+
import { tool as tool21 } from "@anthropic-ai/claude-agent-sdk";
|
|
18944
19726
|
function createPersonaTools(ctx, marvinDir) {
|
|
18945
19727
|
return [
|
|
18946
|
-
|
|
19728
|
+
tool21(
|
|
18947
19729
|
"set_persona",
|
|
18948
19730
|
"Set the active persona for this session. Returns full guidance for the selected persona including behavioral rules, allowed document types, and scope. Call this before working to ensure persona-appropriate behavior.",
|
|
18949
19731
|
{
|
|
@@ -18973,7 +19755,7 @@ ${summaries}`
|
|
|
18973
19755
|
};
|
|
18974
19756
|
}
|
|
18975
19757
|
),
|
|
18976
|
-
|
|
19758
|
+
tool21(
|
|
18977
19759
|
"get_persona_guidance",
|
|
18978
19760
|
"Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
|
|
18979
19761
|
{
|
|
@@ -19204,20 +19986,23 @@ async function skillsInstallCommand(skillId, options) {
|
|
|
19204
19986
|
console.log(chalk10.red("Please specify a persona with --as <persona>."));
|
|
19205
19987
|
return;
|
|
19206
19988
|
}
|
|
19989
|
+
const targets = persona === "all" ? listPersonas().map((p) => p.id) : [persona];
|
|
19207
19990
|
const config2 = loadProjectConfig(project.marvinDir);
|
|
19208
19991
|
if (!config2.skills) {
|
|
19209
19992
|
config2.skills = {};
|
|
19210
19993
|
}
|
|
19211
|
-
|
|
19212
|
-
config2.skills[
|
|
19213
|
-
|
|
19214
|
-
|
|
19215
|
-
|
|
19216
|
-
|
|
19994
|
+
for (const target of targets) {
|
|
19995
|
+
if (!config2.skills[target]) {
|
|
19996
|
+
config2.skills[target] = [];
|
|
19997
|
+
}
|
|
19998
|
+
if (config2.skills[target].includes(skillId)) {
|
|
19999
|
+
console.log(chalk10.yellow(`Skill "${skillId}" is already assigned to ${target}.`));
|
|
20000
|
+
continue;
|
|
20001
|
+
}
|
|
20002
|
+
config2.skills[target].push(skillId);
|
|
20003
|
+
console.log(chalk10.green(`Assigned skill "${skillId}" to ${target}.`));
|
|
19217
20004
|
}
|
|
19218
|
-
config2.skills[persona].push(skillId);
|
|
19219
20005
|
saveProjectConfig(project.marvinDir, config2);
|
|
19220
|
-
console.log(chalk10.green(`Assigned skill "${skillId}" to ${persona}.`));
|
|
19221
20006
|
}
|
|
19222
20007
|
async function skillsRemoveCommand(skillId, options) {
|
|
19223
20008
|
const project = loadProject();
|
|
@@ -19226,25 +20011,28 @@ async function skillsRemoveCommand(skillId, options) {
|
|
|
19226
20011
|
console.log(chalk10.red("Please specify a persona with --as <persona>."));
|
|
19227
20012
|
return;
|
|
19228
20013
|
}
|
|
20014
|
+
const targets = persona === "all" ? listPersonas().map((p) => p.id) : [persona];
|
|
19229
20015
|
const config2 = loadProjectConfig(project.marvinDir);
|
|
19230
|
-
|
|
19231
|
-
|
|
19232
|
-
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
|
|
19240
|
-
|
|
19241
|
-
|
|
20016
|
+
for (const target of targets) {
|
|
20017
|
+
if (!config2.skills?.[target]) {
|
|
20018
|
+
console.log(chalk10.yellow(`No skills configured for ${target}.`));
|
|
20019
|
+
continue;
|
|
20020
|
+
}
|
|
20021
|
+
const idx = config2.skills[target].indexOf(skillId);
|
|
20022
|
+
if (idx === -1) {
|
|
20023
|
+
console.log(chalk10.yellow(`Skill "${skillId}" is not assigned to ${target}.`));
|
|
20024
|
+
continue;
|
|
20025
|
+
}
|
|
20026
|
+
config2.skills[target].splice(idx, 1);
|
|
20027
|
+
if (config2.skills[target].length === 0) {
|
|
20028
|
+
delete config2.skills[target];
|
|
20029
|
+
}
|
|
20030
|
+
console.log(chalk10.green(`Removed skill "${skillId}" from ${target}.`));
|
|
19242
20031
|
}
|
|
19243
|
-
if (Object.keys(config2.skills).length === 0) {
|
|
20032
|
+
if (config2.skills && Object.keys(config2.skills).length === 0) {
|
|
19244
20033
|
delete config2.skills;
|
|
19245
20034
|
}
|
|
19246
20035
|
saveProjectConfig(project.marvinDir, config2);
|
|
19247
|
-
console.log(chalk10.green(`Removed skill "${skillId}" from ${persona}.`));
|
|
19248
20036
|
}
|
|
19249
20037
|
async function skillsCreateCommand(name) {
|
|
19250
20038
|
const project = loadProject();
|
|
@@ -20491,12 +21279,94 @@ Contribution: ${options.type}`));
|
|
|
20491
21279
|
});
|
|
20492
21280
|
}
|
|
20493
21281
|
|
|
21282
|
+
// src/reports/gar/render-ascii.ts
|
|
21283
|
+
import chalk16 from "chalk";
|
|
21284
|
+
var STATUS_DOT = {
|
|
21285
|
+
green: chalk16.green("\u25CF"),
|
|
21286
|
+
amber: chalk16.yellow("\u25CF"),
|
|
21287
|
+
red: chalk16.red("\u25CF")
|
|
21288
|
+
};
|
|
21289
|
+
var STATUS_LABEL = {
|
|
21290
|
+
green: chalk16.green.bold("GREEN"),
|
|
21291
|
+
amber: chalk16.yellow.bold("AMBER"),
|
|
21292
|
+
red: chalk16.red.bold("RED")
|
|
21293
|
+
};
|
|
21294
|
+
var SEPARATOR = chalk16.dim("\u2500".repeat(60));
|
|
21295
|
+
function renderAscii(report) {
|
|
21296
|
+
const lines = [];
|
|
21297
|
+
lines.push("");
|
|
21298
|
+
lines.push(chalk16.bold(` GAR Report \xB7 ${report.projectName}`));
|
|
21299
|
+
lines.push(chalk16.dim(` ${report.generatedAt}`));
|
|
21300
|
+
lines.push("");
|
|
21301
|
+
lines.push(` Overall: ${STATUS_LABEL[report.overall]}`);
|
|
21302
|
+
lines.push("");
|
|
21303
|
+
lines.push(` ${SEPARATOR}`);
|
|
21304
|
+
for (const area of report.areas) {
|
|
21305
|
+
lines.push(` ${STATUS_DOT[area.status]} ${chalk16.bold(area.name.padEnd(12))} ${area.summary}`);
|
|
21306
|
+
for (const item of area.items) {
|
|
21307
|
+
lines.push(` ${chalk16.dim("\u2514")} ${item.id} ${item.title}`);
|
|
21308
|
+
}
|
|
21309
|
+
}
|
|
21310
|
+
lines.push(` ${SEPARATOR}`);
|
|
21311
|
+
lines.push("");
|
|
21312
|
+
return lines.join("\n");
|
|
21313
|
+
}
|
|
21314
|
+
|
|
21315
|
+
// src/reports/gar/render-confluence.ts
|
|
21316
|
+
var EMOJI = {
|
|
21317
|
+
green: ":green_circle:",
|
|
21318
|
+
amber: ":yellow_circle:",
|
|
21319
|
+
red: ":red_circle:"
|
|
21320
|
+
};
|
|
21321
|
+
function renderConfluence(report) {
|
|
21322
|
+
const lines = [];
|
|
21323
|
+
lines.push(`# GAR Report \u2014 ${report.projectName}`);
|
|
21324
|
+
lines.push("");
|
|
21325
|
+
lines.push(`**Date:** ${report.generatedAt}`);
|
|
21326
|
+
lines.push(`**Overall:** ${EMOJI[report.overall]} ${report.overall.toUpperCase()}`);
|
|
21327
|
+
lines.push("");
|
|
21328
|
+
lines.push("| Area | Status | Summary |");
|
|
21329
|
+
lines.push("|------|--------|---------|");
|
|
21330
|
+
for (const area of report.areas) {
|
|
21331
|
+
lines.push(
|
|
21332
|
+
`| ${area.name} | ${EMOJI[area.status]} ${area.status.toUpperCase()} | ${area.summary} |`
|
|
21333
|
+
);
|
|
21334
|
+
}
|
|
21335
|
+
lines.push("");
|
|
21336
|
+
for (const area of report.areas) {
|
|
21337
|
+
if (area.items.length === 0) continue;
|
|
21338
|
+
lines.push(`## ${area.name}`);
|
|
21339
|
+
lines.push("");
|
|
21340
|
+
for (const item of area.items) {
|
|
21341
|
+
lines.push(`- **${item.id}** ${item.title}`);
|
|
21342
|
+
}
|
|
21343
|
+
lines.push("");
|
|
21344
|
+
}
|
|
21345
|
+
return lines.join("\n");
|
|
21346
|
+
}
|
|
21347
|
+
|
|
21348
|
+
// src/cli/commands/report.ts
|
|
21349
|
+
async function garReportCommand(options) {
|
|
21350
|
+
const project = loadProject();
|
|
21351
|
+
const plugin = resolvePlugin(project.config.methodology);
|
|
21352
|
+
const registrations = plugin?.documentTypeRegistrations ?? [];
|
|
21353
|
+
const store = new DocumentStore(project.marvinDir, registrations);
|
|
21354
|
+
const metrics = collectGarMetrics(store);
|
|
21355
|
+
const report = evaluateGar(project.config.name, metrics);
|
|
21356
|
+
const format = options.format ?? "ascii";
|
|
21357
|
+
if (format === "confluence") {
|
|
21358
|
+
console.log(renderConfluence(report));
|
|
21359
|
+
} else {
|
|
21360
|
+
console.log(renderAscii(report));
|
|
21361
|
+
}
|
|
21362
|
+
}
|
|
21363
|
+
|
|
20494
21364
|
// src/cli/program.ts
|
|
20495
21365
|
function createProgram() {
|
|
20496
21366
|
const program2 = new Command();
|
|
20497
21367
|
program2.name("marvin").description(
|
|
20498
21368
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
20499
|
-
).version("0.2.
|
|
21369
|
+
).version("0.2.8");
|
|
20500
21370
|
program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
20501
21371
|
await initCommand();
|
|
20502
21372
|
});
|
|
@@ -20566,6 +21436,13 @@ function createProgram() {
|
|
|
20566
21436
|
skillsCmd.command("migrate").description("Migrate YAML skill files to SKILL.md directory format").action(async () => {
|
|
20567
21437
|
await skillsMigrateCommand();
|
|
20568
21438
|
});
|
|
21439
|
+
const reportCmd = program2.command("report").description("Generate project reports");
|
|
21440
|
+
reportCmd.command("gar").description("Generate a Green/Amber/Red status report").option(
|
|
21441
|
+
"--format <format>",
|
|
21442
|
+
"Output format: ascii or confluence (default: ascii)"
|
|
21443
|
+
).action(async (options) => {
|
|
21444
|
+
await garReportCommand(options);
|
|
21445
|
+
});
|
|
20569
21446
|
return program2;
|
|
20570
21447
|
}
|
|
20571
21448
|
|