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.
@@ -14971,6 +14971,128 @@ function createMeetingTools(store) {
14971
14971
 
14972
14972
  // src/plugins/builtin/tools/reports.ts
14973
14973
  import { tool as tool8 } from "@anthropic-ai/claude-agent-sdk";
14974
+
14975
+ // src/reports/gar/collector.ts
14976
+ function collectGarMetrics(store) {
14977
+ const allActions = store.list({ type: "action" });
14978
+ const openActions = allActions.filter((d) => d.frontmatter.status === "open");
14979
+ const doneActions = allActions.filter((d) => d.frontmatter.status === "done");
14980
+ const allDocs = store.list();
14981
+ const blockedItems = allDocs.filter(
14982
+ (d) => d.frontmatter.tags?.includes("blocked")
14983
+ );
14984
+ const overdueItems = allDocs.filter(
14985
+ (d) => d.frontmatter.tags?.includes("overdue")
14986
+ );
14987
+ const openQuestions = store.list({ type: "question", status: "open" });
14988
+ const riskItems = allDocs.filter(
14989
+ (d) => d.frontmatter.tags?.includes("risk")
14990
+ );
14991
+ const unownedActions = openActions.filter((d) => !d.frontmatter.owner);
14992
+ const total = allActions.length;
14993
+ const done = doneActions.length;
14994
+ const completionPct = total > 0 ? Math.round(done / total * 100) : 100;
14995
+ const scheduleItems = [
14996
+ ...blockedItems,
14997
+ ...overdueItems
14998
+ ].filter(
14999
+ (d, i, arr) => arr.findIndex((x) => x.frontmatter.id === d.frontmatter.id) === i
15000
+ ).map((d) => ({ id: d.frontmatter.id, title: d.frontmatter.title }));
15001
+ const qualityItems = [
15002
+ ...riskItems,
15003
+ ...openQuestions
15004
+ ].filter(
15005
+ (d, i, arr) => arr.findIndex((x) => x.frontmatter.id === d.frontmatter.id) === i
15006
+ ).map((d) => ({ id: d.frontmatter.id, title: d.frontmatter.title }));
15007
+ const resourceItems = unownedActions.map((d) => ({
15008
+ id: d.frontmatter.id,
15009
+ title: d.frontmatter.title
15010
+ }));
15011
+ return {
15012
+ scope: {
15013
+ total,
15014
+ open: openActions.length,
15015
+ done,
15016
+ completionPct
15017
+ },
15018
+ schedule: {
15019
+ blocked: blockedItems.length,
15020
+ overdue: overdueItems.length,
15021
+ items: scheduleItems
15022
+ },
15023
+ quality: {
15024
+ risks: riskItems.length,
15025
+ openQuestions: openQuestions.length,
15026
+ items: qualityItems
15027
+ },
15028
+ resources: {
15029
+ unowned: unownedActions.length,
15030
+ items: resourceItems
15031
+ }
15032
+ };
15033
+ }
15034
+
15035
+ // src/reports/gar/evaluator.ts
15036
+ function worstStatus(statuses) {
15037
+ if (statuses.includes("red")) return "red";
15038
+ if (statuses.includes("amber")) return "amber";
15039
+ return "green";
15040
+ }
15041
+ function evaluateGar(projectName, metrics) {
15042
+ const areas = [];
15043
+ const scopePct = metrics.scope.completionPct;
15044
+ const scopeStatus = scopePct >= 70 ? "green" : scopePct >= 40 ? "amber" : "red";
15045
+ areas.push({
15046
+ name: "Scope",
15047
+ status: scopeStatus,
15048
+ summary: `${scopePct}% complete (${metrics.scope.done}/${metrics.scope.total})`,
15049
+ items: []
15050
+ });
15051
+ const scheduleCount = metrics.schedule.blocked + metrics.schedule.overdue;
15052
+ const scheduleStatus = scheduleCount === 0 ? "green" : scheduleCount <= 2 ? "amber" : "red";
15053
+ const scheduleParts = [];
15054
+ if (metrics.schedule.blocked > 0)
15055
+ scheduleParts.push(`${metrics.schedule.blocked} blocked`);
15056
+ if (metrics.schedule.overdue > 0)
15057
+ scheduleParts.push(`${metrics.schedule.overdue} overdue`);
15058
+ areas.push({
15059
+ name: "Schedule",
15060
+ status: scheduleStatus,
15061
+ summary: scheduleParts.length > 0 ? scheduleParts.join(", ") : "on track",
15062
+ items: metrics.schedule.items
15063
+ });
15064
+ const qualityCount = metrics.quality.risks + metrics.quality.openQuestions;
15065
+ const qualityStatus = qualityCount === 0 ? "green" : qualityCount <= 2 ? "amber" : "red";
15066
+ const qualityParts = [];
15067
+ if (metrics.quality.risks > 0)
15068
+ qualityParts.push(`${metrics.quality.risks} risk(s)`);
15069
+ if (metrics.quality.openQuestions > 0)
15070
+ qualityParts.push(`${metrics.quality.openQuestions} open question(s)`);
15071
+ areas.push({
15072
+ name: "Quality",
15073
+ status: qualityStatus,
15074
+ summary: qualityParts.length > 0 ? qualityParts.join(", ") : "no issues",
15075
+ items: metrics.quality.items
15076
+ });
15077
+ const resourceCount = metrics.resources.unowned;
15078
+ const resourceStatus = resourceCount === 0 ? "green" : resourceCount <= 2 ? "amber" : "red";
15079
+ areas.push({
15080
+ name: "Resources",
15081
+ status: resourceStatus,
15082
+ summary: resourceCount > 0 ? `${resourceCount} unowned action(s)` : "all assigned",
15083
+ items: metrics.resources.items
15084
+ });
15085
+ const overall = worstStatus(areas.map((a) => a.status));
15086
+ return {
15087
+ projectName,
15088
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
15089
+ overall,
15090
+ areas,
15091
+ metrics
15092
+ };
15093
+ }
15094
+
15095
+ // src/plugins/builtin/tools/reports.ts
14974
15096
  function createReportTools(store) {
14975
15097
  return [
14976
15098
  tool8(
@@ -15059,41 +15181,10 @@ function createReportTools(store) {
15059
15181
  "Generate a Green-Amber-Red report with metrics across scope, schedule, quality, and resources",
15060
15182
  {},
15061
15183
  async () => {
15062
- const allActions = store.list({ type: "action" });
15063
- const openActions = allActions.filter((d) => d.frontmatter.status === "open");
15064
- const doneActions = allActions.filter((d) => d.frontmatter.status === "done");
15065
- const allDocs = store.list();
15066
- const blockedItems = allDocs.filter(
15067
- (d) => d.frontmatter.tags?.includes("blocked")
15068
- );
15069
- const overdueItems = allDocs.filter(
15070
- (d) => d.frontmatter.tags?.includes("overdue")
15071
- );
15072
- const openQuestions = store.list({ type: "question", status: "open" });
15073
- const riskItems = allDocs.filter(
15074
- (d) => d.frontmatter.tags?.includes("risk")
15075
- );
15076
- const unownedActions = openActions.filter((d) => !d.frontmatter.owner);
15077
- const areas = {
15078
- scope: {
15079
- total: allActions.length,
15080
- open: openActions.length,
15081
- done: doneActions.length
15082
- },
15083
- schedule: {
15084
- blocked: blockedItems.length,
15085
- overdue: overdueItems.length
15086
- },
15087
- quality: {
15088
- openQuestions: openQuestions.length,
15089
- risks: riskItems.length
15090
- },
15091
- resources: {
15092
- unowned: unownedActions.length
15093
- }
15094
- };
15184
+ const metrics = collectGarMetrics(store);
15185
+ const report = evaluateGar("project", metrics);
15095
15186
  return {
15096
- content: [{ type: "text", text: JSON.stringify({ areas }, null, 2) }]
15187
+ content: [{ type: "text", text: JSON.stringify(report, null, 2) }]
15097
15188
  };
15098
15189
  },
15099
15190
  { annotations: { readOnly: true } }
@@ -15873,6 +15964,188 @@ function createSprintTools(store) {
15873
15964
  ];
15874
15965
  }
15875
15966
 
15967
+ // src/plugins/builtin/tools/sprint-planning.ts
15968
+ import { tool as tool13 } from "@anthropic-ai/claude-agent-sdk";
15969
+ var PRIORITY_ORDER = {
15970
+ critical: 0,
15971
+ high: 1,
15972
+ medium: 2,
15973
+ low: 3
15974
+ };
15975
+ function priorityRank(p) {
15976
+ return PRIORITY_ORDER[p ?? ""] ?? 99;
15977
+ }
15978
+ function createSprintPlanningTools(store) {
15979
+ return [
15980
+ tool13(
15981
+ "gather_sprint_planning_context",
15982
+ "Aggregate all planning-relevant data for proposing the next sprint: approved features, backlog epics, active sprint, velocity reference, blockers, and summary stats",
15983
+ {
15984
+ focusFeature: external_exports.string().optional().describe("Filter backlog to epics of a specific feature ID (e.g. 'F-001')"),
15985
+ sprintDurationDays: external_exports.number().optional().describe("Expected sprint duration in days \u2014 passed through for capacity reasoning")
15986
+ },
15987
+ async (args) => {
15988
+ const features = store.list({ type: "feature" });
15989
+ const epics = store.list({ type: "epic" });
15990
+ const sprints = store.list({ type: "sprint" });
15991
+ const questions = store.list({ type: "question", status: "open" });
15992
+ const contributions = store.list({ type: "contribution" });
15993
+ const approvedFeatures = features.filter((f) => f.frontmatter.status === "approved").sort((a, b) => priorityRank(a.frontmatter.priority) - priorityRank(b.frontmatter.priority)).map((f) => {
15994
+ const linkedEpics = epics.filter((e) => e.frontmatter.linkedFeature === f.frontmatter.id);
15995
+ const epicsByStatus = {};
15996
+ for (const e of linkedEpics) {
15997
+ epicsByStatus[e.frontmatter.status] = (epicsByStatus[e.frontmatter.status] ?? 0) + 1;
15998
+ }
15999
+ return {
16000
+ id: f.frontmatter.id,
16001
+ title: f.frontmatter.title,
16002
+ priority: f.frontmatter.priority,
16003
+ owner: f.frontmatter.owner,
16004
+ epicCount: linkedEpics.length,
16005
+ epicsByStatus
16006
+ };
16007
+ });
16008
+ const assignedEpicIds = /* @__PURE__ */ new Set();
16009
+ for (const sp of sprints) {
16010
+ const linked = sp.frontmatter.linkedEpics ?? [];
16011
+ for (const id of linked) assignedEpicIds.add(id);
16012
+ }
16013
+ const featureMap = new Map(features.map((f) => [f.frontmatter.id, f]));
16014
+ let backlogEpics = epics.filter(
16015
+ (e) => !assignedEpicIds.has(e.frontmatter.id) && e.frontmatter.status !== "done"
16016
+ );
16017
+ if (args.focusFeature) {
16018
+ backlogEpics = backlogEpics.filter(
16019
+ (e) => e.frontmatter.linkedFeature === args.focusFeature
16020
+ );
16021
+ }
16022
+ const backlog = backlogEpics.sort((a, b) => {
16023
+ const fa = featureMap.get(a.frontmatter.linkedFeature);
16024
+ const fb = featureMap.get(b.frontmatter.linkedFeature);
16025
+ return priorityRank(fa?.frontmatter.priority) - priorityRank(fb?.frontmatter.priority);
16026
+ }).map((e) => {
16027
+ const parent = featureMap.get(e.frontmatter.linkedFeature);
16028
+ return {
16029
+ id: e.frontmatter.id,
16030
+ title: e.frontmatter.title,
16031
+ status: e.frontmatter.status,
16032
+ linkedFeature: e.frontmatter.linkedFeature,
16033
+ featureTitle: parent?.frontmatter.title ?? null,
16034
+ featurePriority: parent?.frontmatter.priority ?? null,
16035
+ estimatedEffort: e.frontmatter.estimatedEffort ?? null,
16036
+ targetDate: e.frontmatter.targetDate ?? null
16037
+ };
16038
+ });
16039
+ const activeSprintDoc = sprints.find((s) => s.frontmatter.status === "active") ?? null;
16040
+ let activeSprint = null;
16041
+ if (activeSprintDoc) {
16042
+ const linkedEpicIds = activeSprintDoc.frontmatter.linkedEpics ?? [];
16043
+ const linkedEpics = linkedEpicIds.map((epicId) => {
16044
+ const epic = store.get(epicId);
16045
+ return epic ? { id: epicId, title: epic.frontmatter.title, status: epic.frontmatter.status } : { id: epicId, title: "(not found)", status: "unknown" };
16046
+ });
16047
+ const allDocs = store.list();
16048
+ const sprintTag = `sprint:${activeSprintDoc.frontmatter.id}`;
16049
+ const workItems = allDocs.filter(
16050
+ (d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.tags?.includes(sprintTag)
16051
+ );
16052
+ const doneCount = workItems.filter(
16053
+ (d) => d.frontmatter.status === "done" || d.frontmatter.status === "resolved" || d.frontmatter.status === "closed"
16054
+ ).length;
16055
+ const completionPct = workItems.length > 0 ? Math.round(doneCount / workItems.length * 100) : 0;
16056
+ activeSprint = {
16057
+ id: activeSprintDoc.frontmatter.id,
16058
+ title: activeSprintDoc.frontmatter.title,
16059
+ goal: activeSprintDoc.frontmatter.goal,
16060
+ startDate: activeSprintDoc.frontmatter.startDate,
16061
+ endDate: activeSprintDoc.frontmatter.endDate,
16062
+ linkedEpics,
16063
+ workItems: { total: workItems.length, done: doneCount, completionPct }
16064
+ };
16065
+ }
16066
+ const completedSprints = sprints.filter((s) => s.frontmatter.status === "completed").sort((a, b) => {
16067
+ const da = a.frontmatter.endDate ?? "";
16068
+ const db = b.frontmatter.endDate ?? "";
16069
+ return da < db ? 1 : da > db ? -1 : 0;
16070
+ }).slice(0, 2);
16071
+ const velocityReference = completedSprints.map((sp) => {
16072
+ const linkedEpicIds = sp.frontmatter.linkedEpics ?? [];
16073
+ const efforts = [];
16074
+ for (const epicId of linkedEpicIds) {
16075
+ const epic = store.get(epicId);
16076
+ if (epic?.frontmatter.estimatedEffort) {
16077
+ efforts.push(String(epic.frontmatter.estimatedEffort));
16078
+ }
16079
+ }
16080
+ const allDocs = store.list();
16081
+ const sprintTag = `sprint:${sp.frontmatter.id}`;
16082
+ const workItems = allDocs.filter(
16083
+ (d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.tags?.includes(sprintTag)
16084
+ );
16085
+ return {
16086
+ id: sp.frontmatter.id,
16087
+ title: sp.frontmatter.title,
16088
+ startDate: sp.frontmatter.startDate,
16089
+ endDate: sp.frontmatter.endDate,
16090
+ epicCount: linkedEpicIds.length,
16091
+ efforts,
16092
+ workItemCount: workItems.length
16093
+ };
16094
+ });
16095
+ const openBlockerContributions = contributions.filter(
16096
+ (c) => c.frontmatter.status === "open" && (c.frontmatter.contributionType === "risk-finding" || c.frontmatter.contributionType === "blocker-report")
16097
+ );
16098
+ const blockers = {
16099
+ openQuestions: questions.map((q) => ({
16100
+ id: q.frontmatter.id,
16101
+ title: q.frontmatter.title
16102
+ })),
16103
+ openRiskAndBlockerContributions: openBlockerContributions.map((c) => ({
16104
+ id: c.frontmatter.id,
16105
+ title: c.frontmatter.title,
16106
+ contributionType: c.frontmatter.contributionType
16107
+ }))
16108
+ };
16109
+ const totalBacklogEfforts = backlog.filter((e) => e.estimatedEffort !== null).map((e) => String(e.estimatedEffort));
16110
+ const approvedFeaturesWithNoEpics = approvedFeatures.filter((f) => f.epicCount === 0).map((f) => ({ id: f.id, title: f.title }));
16111
+ const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
16112
+ const epicsAtRisk = epics.filter((e) => {
16113
+ if (e.frontmatter.status === "done") return false;
16114
+ if (e.frontmatter.targetDate && e.frontmatter.targetDate < now) return true;
16115
+ const parent = featureMap.get(e.frontmatter.linkedFeature);
16116
+ if (parent?.frontmatter.status === "deferred") return true;
16117
+ return false;
16118
+ }).map((e) => ({
16119
+ id: e.frontmatter.id,
16120
+ title: e.frontmatter.title,
16121
+ reason: e.frontmatter.targetDate && e.frontmatter.targetDate < now ? "past-target-date" : "deferred-feature"
16122
+ }));
16123
+ const plannedSprintCount = sprints.filter((s) => s.frontmatter.status === "planned").length;
16124
+ const summary = {
16125
+ totalBacklogEpics: backlog.length,
16126
+ totalBacklogEfforts,
16127
+ approvedFeaturesWithNoEpics,
16128
+ epicsAtRisk,
16129
+ plannedSprintCount
16130
+ };
16131
+ const result = {
16132
+ approvedFeatures,
16133
+ backlog,
16134
+ activeSprint,
16135
+ velocityReference,
16136
+ blockers,
16137
+ summary,
16138
+ ...args.sprintDurationDays !== void 0 ? { sprintDurationDays: args.sprintDurationDays } : {}
16139
+ };
16140
+ return {
16141
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
16142
+ };
16143
+ },
16144
+ { annotations: { readOnly: true } }
16145
+ )
16146
+ ];
16147
+ }
16148
+
15876
16149
  // src/plugins/common.ts
15877
16150
  var COMMON_REGISTRATIONS = [
15878
16151
  { type: "meeting", dirName: "meetings", idPrefix: "M" },
@@ -15889,7 +16162,8 @@ function createCommonTools(store) {
15889
16162
  ...createFeatureTools(store),
15890
16163
  ...createEpicTools(store),
15891
16164
  ...createContributionTools(store),
15892
- ...createSprintTools(store)
16165
+ ...createSprintTools(store),
16166
+ ...createSprintPlanningTools(store)
15893
16167
  ];
15894
16168
  }
15895
16169
 
@@ -15962,7 +16236,12 @@ var genericAgilePlugin = {
15962
16236
  - **list_sprints** / **get_sprint**: View sprints to understand iteration scope and delivery dates.
15963
16237
  - **update_sprint**: Assign epics to sprints by updating linkedEpics when breaking features into work.
15964
16238
  - Tag technical actions and decisions with \`sprint:SP-xxx\` to associate them with a sprint.
15965
- - Use **generate_sprint_progress** to track technical work completion within an iteration.`,
16239
+ - Use **generate_sprint_progress** to track technical work completion within an iteration.
16240
+
16241
+ **Sprint Planning:**
16242
+ - When asked to plan or propose a sprint, ALWAYS call **gather_sprint_planning_context** first.
16243
+ - Focus on: technical readiness of each epic, open technical questions or spikes, effort balance across the sprint, and feature coverage.
16244
+ - Present a structured sprint proposal with technical rationale for each selected epic, known technical risks, and any prerequisite work that should be completed first.`,
15966
16245
  "delivery-manager": `You track delivery across features and epics, manage schedules, and report on progress.
15967
16246
 
15968
16247
  **Report Tools:**
@@ -16010,7 +16289,13 @@ var genericAgilePlugin = {
16010
16289
  - Assign epics to sprints via linkedEpics.
16011
16290
  - Tag work items (actions, decisions, questions) with \`sprint:SP-xxx\` for sprint scoping.
16012
16291
  - Track delivery dates and flag at-risk sprints.
16013
- - Register past/completed sprints for historical tracking.`,
16292
+ - Register past/completed sprints for historical tracking.
16293
+
16294
+ **Sprint Planning:**
16295
+ - 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.
16296
+ - Reason through: priority (critical/high features first), capacity (compare backlog effort to velocity reference), dependencies and blockers, balance across features, and risk.
16297
+ - Present a structured sprint proposal: title, goal, suggested dates, selected epics with rationale for each, excluded epics with reason, and identified risks.
16298
+ - After user confirmation, use **create_sprint** with the agreed epics to persist the sprint.`,
16014
16299
  "*": `You have access to feature, epic, sprint, and meeting tools for project coordination:
16015
16300
 
16016
16301
  **Features** (F-xxx): Product capabilities defined by the Product Owner. Features progress through draft \u2192 approved \u2192 done.
@@ -16033,10 +16318,10 @@ var genericAgilePlugin = {
16033
16318
  };
16034
16319
 
16035
16320
  // src/plugins/builtin/tools/use-cases.ts
16036
- import { tool as tool13 } from "@anthropic-ai/claude-agent-sdk";
16321
+ import { tool as tool14 } from "@anthropic-ai/claude-agent-sdk";
16037
16322
  function createUseCaseTools(store) {
16038
16323
  return [
16039
- tool13(
16324
+ tool14(
16040
16325
  "list_use_cases",
16041
16326
  "List all extension use cases, optionally filtered by status or extension type",
16042
16327
  {
@@ -16066,7 +16351,7 @@ function createUseCaseTools(store) {
16066
16351
  },
16067
16352
  { annotations: { readOnly: true } }
16068
16353
  ),
16069
- tool13(
16354
+ tool14(
16070
16355
  "get_use_case",
16071
16356
  "Get the full content of a specific use case by ID",
16072
16357
  { id: external_exports.string().describe("Use case ID (e.g. 'UC-001')") },
@@ -16093,7 +16378,7 @@ function createUseCaseTools(store) {
16093
16378
  },
16094
16379
  { annotations: { readOnly: true } }
16095
16380
  ),
16096
- tool13(
16381
+ tool14(
16097
16382
  "create_use_case",
16098
16383
  "Create a new extension use case definition (Phase 1: Assess Extension Use Case)",
16099
16384
  {
@@ -16127,7 +16412,7 @@ function createUseCaseTools(store) {
16127
16412
  };
16128
16413
  }
16129
16414
  ),
16130
- tool13(
16415
+ tool14(
16131
16416
  "update_use_case",
16132
16417
  "Update an existing extension use case",
16133
16418
  {
@@ -16157,10 +16442,10 @@ function createUseCaseTools(store) {
16157
16442
  }
16158
16443
 
16159
16444
  // src/plugins/builtin/tools/tech-assessments.ts
16160
- import { tool as tool14 } from "@anthropic-ai/claude-agent-sdk";
16445
+ import { tool as tool15 } from "@anthropic-ai/claude-agent-sdk";
16161
16446
  function createTechAssessmentTools(store) {
16162
16447
  return [
16163
- tool14(
16448
+ tool15(
16164
16449
  "list_tech_assessments",
16165
16450
  "List all technology assessments, optionally filtered by status",
16166
16451
  {
@@ -16191,7 +16476,7 @@ function createTechAssessmentTools(store) {
16191
16476
  },
16192
16477
  { annotations: { readOnly: true } }
16193
16478
  ),
16194
- tool14(
16479
+ tool15(
16195
16480
  "get_tech_assessment",
16196
16481
  "Get the full content of a specific technology assessment by ID",
16197
16482
  { id: external_exports.string().describe("Tech assessment ID (e.g. 'TA-001')") },
@@ -16218,7 +16503,7 @@ function createTechAssessmentTools(store) {
16218
16503
  },
16219
16504
  { annotations: { readOnly: true } }
16220
16505
  ),
16221
- tool14(
16506
+ tool15(
16222
16507
  "create_tech_assessment",
16223
16508
  "Create a new technology assessment linked to an assessed/approved use case (Phase 2: Assess Extension Technology)",
16224
16509
  {
@@ -16289,7 +16574,7 @@ function createTechAssessmentTools(store) {
16289
16574
  };
16290
16575
  }
16291
16576
  ),
16292
- tool14(
16577
+ tool15(
16293
16578
  "update_tech_assessment",
16294
16579
  "Update an existing technology assessment. The linked use case cannot be changed.",
16295
16580
  {
@@ -16319,10 +16604,10 @@ function createTechAssessmentTools(store) {
16319
16604
  }
16320
16605
 
16321
16606
  // src/plugins/builtin/tools/extension-designs.ts
16322
- import { tool as tool15 } from "@anthropic-ai/claude-agent-sdk";
16607
+ import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
16323
16608
  function createExtensionDesignTools(store) {
16324
16609
  return [
16325
- tool15(
16610
+ tool16(
16326
16611
  "list_extension_designs",
16327
16612
  "List all extension designs, optionally filtered by status",
16328
16613
  {
@@ -16352,7 +16637,7 @@ function createExtensionDesignTools(store) {
16352
16637
  },
16353
16638
  { annotations: { readOnly: true } }
16354
16639
  ),
16355
- tool15(
16640
+ tool16(
16356
16641
  "get_extension_design",
16357
16642
  "Get the full content of a specific extension design by ID",
16358
16643
  { id: external_exports.string().describe("Extension design ID (e.g. 'XD-001')") },
@@ -16379,7 +16664,7 @@ function createExtensionDesignTools(store) {
16379
16664
  },
16380
16665
  { annotations: { readOnly: true } }
16381
16666
  ),
16382
- tool15(
16667
+ tool16(
16383
16668
  "create_extension_design",
16384
16669
  "Create a new extension design linked to a recommended tech assessment (Phase 3: Define Extension Target Solution)",
16385
16670
  {
@@ -16447,7 +16732,7 @@ function createExtensionDesignTools(store) {
16447
16732
  };
16448
16733
  }
16449
16734
  ),
16450
- tool15(
16735
+ tool16(
16451
16736
  "update_extension_design",
16452
16737
  "Update an existing extension design. The linked tech assessment cannot be changed.",
16453
16738
  {
@@ -16476,10 +16761,10 @@ function createExtensionDesignTools(store) {
16476
16761
  }
16477
16762
 
16478
16763
  // src/plugins/builtin/tools/aem-reports.ts
16479
- import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
16764
+ import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
16480
16765
  function createAemReportTools(store) {
16481
16766
  return [
16482
- tool16(
16767
+ tool17(
16483
16768
  "generate_extension_portfolio",
16484
16769
  "Generate a portfolio view of all use cases with their linked tech assessments and extension designs",
16485
16770
  {},
@@ -16531,7 +16816,7 @@ function createAemReportTools(store) {
16531
16816
  },
16532
16817
  { annotations: { readOnly: true } }
16533
16818
  ),
16534
- tool16(
16819
+ tool17(
16535
16820
  "generate_tech_readiness",
16536
16821
  "Generate a BTP technology readiness report showing service coverage and gaps across assessments",
16537
16822
  {},
@@ -16583,7 +16868,7 @@ function createAemReportTools(store) {
16583
16868
  },
16584
16869
  { annotations: { readOnly: true } }
16585
16870
  ),
16586
- tool16(
16871
+ tool17(
16587
16872
  "generate_phase_status",
16588
16873
  "Generate a phase progress report showing artifact counts and readiness per AEM phase",
16589
16874
  {},
@@ -16645,11 +16930,11 @@ function createAemReportTools(store) {
16645
16930
  import * as fs6 from "fs";
16646
16931
  import * as path6 from "path";
16647
16932
  import * as YAML4 from "yaml";
16648
- import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
16933
+ import { tool as tool18 } from "@anthropic-ai/claude-agent-sdk";
16649
16934
  var PHASES = ["assess-use-case", "assess-technology", "define-solution"];
16650
16935
  function createAemPhaseTools(store, marvinDir) {
16651
16936
  return [
16652
- tool17(
16937
+ tool18(
16653
16938
  "get_current_phase",
16654
16939
  "Get the current AEM phase from project configuration",
16655
16940
  {},
@@ -16670,7 +16955,7 @@ function createAemPhaseTools(store, marvinDir) {
16670
16955
  },
16671
16956
  { annotations: { readOnly: true } }
16672
16957
  ),
16673
- tool17(
16958
+ tool18(
16674
16959
  "advance_phase",
16675
16960
  "Advance to the next AEM phase. Performs soft gate checks and warns if artifacts are incomplete, but does not block.",
16676
16961
  {
@@ -16977,9 +17262,495 @@ Be thorough but concise. Focus on actionable insights.`,
16977
17262
  ]
16978
17263
  };
16979
17264
 
17265
+ // src/skills/builtin/jira/tools.ts
17266
+ import { tool as tool19 } from "@anthropic-ai/claude-agent-sdk";
17267
+
17268
+ // src/skills/builtin/jira/client.ts
17269
+ var JiraClient = class {
17270
+ baseUrl;
17271
+ authHeader;
17272
+ constructor(config2) {
17273
+ this.baseUrl = `https://${config2.host}/rest/api/2`;
17274
+ this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
17275
+ }
17276
+ async request(path10, method = "GET", body) {
17277
+ const url2 = `${this.baseUrl}${path10}`;
17278
+ const headers = {
17279
+ Authorization: this.authHeader,
17280
+ "Content-Type": "application/json",
17281
+ Accept: "application/json"
17282
+ };
17283
+ const response = await fetch(url2, {
17284
+ method,
17285
+ headers,
17286
+ body: body ? JSON.stringify(body) : void 0
17287
+ });
17288
+ if (!response.ok) {
17289
+ const text = await response.text().catch(() => "");
17290
+ throw new Error(
17291
+ `Jira API error ${response.status} ${method} ${path10}: ${text}`
17292
+ );
17293
+ }
17294
+ if (response.status === 204) return void 0;
17295
+ return response.json();
17296
+ }
17297
+ async searchIssues(jql, maxResults = 50) {
17298
+ const params = new URLSearchParams({
17299
+ jql,
17300
+ maxResults: String(maxResults)
17301
+ });
17302
+ return this.request(`/search?${params}`);
17303
+ }
17304
+ async getIssue(key) {
17305
+ return this.request(`/issue/${encodeURIComponent(key)}`);
17306
+ }
17307
+ async createIssue(fields) {
17308
+ return this.request("/issue", "POST", { fields });
17309
+ }
17310
+ async updateIssue(key, fields) {
17311
+ await this.request(
17312
+ `/issue/${encodeURIComponent(key)}`,
17313
+ "PUT",
17314
+ { fields }
17315
+ );
17316
+ }
17317
+ async addComment(key, body) {
17318
+ await this.request(
17319
+ `/issue/${encodeURIComponent(key)}/comment`,
17320
+ "POST",
17321
+ { body }
17322
+ );
17323
+ }
17324
+ };
17325
+ function createJiraClient() {
17326
+ const host = process.env.JIRA_HOST;
17327
+ const email3 = process.env.JIRA_EMAIL;
17328
+ const apiToken = process.env.JIRA_API_TOKEN;
17329
+ if (!host || !email3 || !apiToken) return null;
17330
+ return new JiraClient({ host, email: email3, apiToken });
17331
+ }
17332
+
17333
+ // src/skills/builtin/jira/tools.ts
17334
+ var JIRA_TYPE = "jira-issue";
17335
+ function jiraNotConfiguredError() {
17336
+ return {
17337
+ content: [
17338
+ {
17339
+ type: "text",
17340
+ text: "Jira is not configured. Set JIRA_HOST, JIRA_EMAIL, and JIRA_API_TOKEN environment variables."
17341
+ }
17342
+ ],
17343
+ isError: true
17344
+ };
17345
+ }
17346
+ function mapJiraStatus(jiraStatus) {
17347
+ const lower = jiraStatus.toLowerCase();
17348
+ if (lower === "done" || lower === "closed" || lower === "resolved") return "done";
17349
+ if (lower === "in progress" || lower === "in review") return "in-progress";
17350
+ return "open";
17351
+ }
17352
+ function jiraIssueToFrontmatter(issue2, host, linkedArtifacts) {
17353
+ return {
17354
+ title: issue2.fields.summary,
17355
+ status: mapJiraStatus(issue2.fields.status.name),
17356
+ jiraKey: issue2.key,
17357
+ jiraUrl: `https://${host}/browse/${issue2.key}`,
17358
+ issueType: issue2.fields.issuetype.name,
17359
+ priority: issue2.fields.priority?.name ?? "None",
17360
+ assignee: issue2.fields.assignee?.displayName ?? "",
17361
+ labels: issue2.fields.labels ?? [],
17362
+ linkedArtifacts: linkedArtifacts ?? [],
17363
+ tags: [`jira:${issue2.key}`],
17364
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
17365
+ };
17366
+ }
17367
+ function findByJiraKey(store, jiraKey) {
17368
+ const docs = store.list({ type: JIRA_TYPE });
17369
+ return docs.find((d) => d.frontmatter.jiraKey === jiraKey);
17370
+ }
17371
+ function createJiraTools(store) {
17372
+ return [
17373
+ // --- Local read tools ---
17374
+ tool19(
17375
+ "list_jira_issues",
17376
+ "List locally synced Jira issues (JI-xxx documents), optionally filtered by status or Jira key",
17377
+ {
17378
+ status: external_exports.enum(["open", "in-progress", "done"]).optional().describe("Filter by local status"),
17379
+ jiraKey: external_exports.string().optional().describe("Filter by Jira issue key (e.g. 'PROJ-123')")
17380
+ },
17381
+ async (args) => {
17382
+ let docs = store.list({ type: JIRA_TYPE, status: args.status });
17383
+ if (args.jiraKey) {
17384
+ docs = docs.filter((d) => d.frontmatter.jiraKey === args.jiraKey);
17385
+ }
17386
+ const summary = docs.map((d) => ({
17387
+ id: d.frontmatter.id,
17388
+ title: d.frontmatter.title,
17389
+ status: d.frontmatter.status,
17390
+ jiraKey: d.frontmatter.jiraKey,
17391
+ issueType: d.frontmatter.issueType,
17392
+ priority: d.frontmatter.priority,
17393
+ assignee: d.frontmatter.assignee,
17394
+ linkedArtifacts: d.frontmatter.linkedArtifacts
17395
+ }));
17396
+ return {
17397
+ content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
17398
+ };
17399
+ },
17400
+ { annotations: { readOnly: true } }
17401
+ ),
17402
+ tool19(
17403
+ "get_jira_issue",
17404
+ "Get the full content of a locally synced Jira issue by local ID (JI-xxx) or Jira key (PROJ-123)",
17405
+ {
17406
+ id: external_exports.string().describe("Local ID (e.g. 'JI-001') or Jira key (e.g. 'PROJ-123')")
17407
+ },
17408
+ async (args) => {
17409
+ let doc = store.get(args.id);
17410
+ if (!doc) {
17411
+ doc = findByJiraKey(store, args.id);
17412
+ }
17413
+ if (!doc) {
17414
+ return {
17415
+ content: [{ type: "text", text: `Jira issue ${args.id} not found locally` }],
17416
+ isError: true
17417
+ };
17418
+ }
17419
+ return {
17420
+ content: [
17421
+ {
17422
+ type: "text",
17423
+ text: JSON.stringify(
17424
+ { ...doc.frontmatter, content: doc.content },
17425
+ null,
17426
+ 2
17427
+ )
17428
+ }
17429
+ ]
17430
+ };
17431
+ },
17432
+ { annotations: { readOnly: true } }
17433
+ ),
17434
+ // --- Jira → Local tools ---
17435
+ tool19(
17436
+ "pull_jira_issue",
17437
+ "Fetch a single Jira issue by key and create/update a local JI-xxx document",
17438
+ {
17439
+ key: external_exports.string().describe("Jira issue key (e.g. 'PROJ-123')")
17440
+ },
17441
+ async (args) => {
17442
+ const client = createJiraClient();
17443
+ if (!client) return jiraNotConfiguredError();
17444
+ const issue2 = await client.getIssue(args.key);
17445
+ const host = process.env.JIRA_HOST;
17446
+ const existing = findByJiraKey(store, args.key);
17447
+ if (existing) {
17448
+ const fm2 = jiraIssueToFrontmatter(
17449
+ issue2,
17450
+ host,
17451
+ existing.frontmatter.linkedArtifacts
17452
+ );
17453
+ const doc2 = store.update(
17454
+ existing.frontmatter.id,
17455
+ fm2,
17456
+ issue2.fields.description ?? ""
17457
+ );
17458
+ return {
17459
+ content: [
17460
+ {
17461
+ type: "text",
17462
+ text: `Updated ${doc2.frontmatter.id} from Jira ${args.key}`
17463
+ }
17464
+ ]
17465
+ };
17466
+ }
17467
+ const fm = jiraIssueToFrontmatter(issue2, host);
17468
+ const doc = store.create(
17469
+ JIRA_TYPE,
17470
+ fm,
17471
+ issue2.fields.description ?? ""
17472
+ );
17473
+ return {
17474
+ content: [
17475
+ {
17476
+ type: "text",
17477
+ text: `Created ${doc.frontmatter.id} from Jira ${args.key}`
17478
+ }
17479
+ ]
17480
+ };
17481
+ }
17482
+ ),
17483
+ tool19(
17484
+ "pull_jira_issues_jql",
17485
+ "Bulk fetch Jira issues via JQL query and create/update local JI-xxx documents",
17486
+ {
17487
+ jql: external_exports.string().describe(`JQL query (e.g. 'project = PROJ AND status = "In Progress"')`),
17488
+ maxResults: external_exports.number().optional().describe("Max issues to fetch (default 50)")
17489
+ },
17490
+ async (args) => {
17491
+ const client = createJiraClient();
17492
+ if (!client) return jiraNotConfiguredError();
17493
+ const result = await client.searchIssues(args.jql, args.maxResults);
17494
+ const host = process.env.JIRA_HOST;
17495
+ const created = [];
17496
+ const updated = [];
17497
+ for (const issue2 of result.issues) {
17498
+ const existing = findByJiraKey(store, issue2.key);
17499
+ if (existing) {
17500
+ const fm = jiraIssueToFrontmatter(
17501
+ issue2,
17502
+ host,
17503
+ existing.frontmatter.linkedArtifacts
17504
+ );
17505
+ store.update(
17506
+ existing.frontmatter.id,
17507
+ fm,
17508
+ issue2.fields.description ?? ""
17509
+ );
17510
+ updated.push(`${existing.frontmatter.id} (${issue2.key})`);
17511
+ } else {
17512
+ const fm = jiraIssueToFrontmatter(issue2, host);
17513
+ const doc = store.create(
17514
+ JIRA_TYPE,
17515
+ fm,
17516
+ issue2.fields.description ?? ""
17517
+ );
17518
+ created.push(`${doc.frontmatter.id} (${issue2.key})`);
17519
+ }
17520
+ }
17521
+ const parts = [
17522
+ `Fetched ${result.issues.length} of ${result.total} matching issues.`
17523
+ ];
17524
+ if (created.length > 0) parts.push(`Created: ${created.join(", ")}`);
17525
+ if (updated.length > 0) parts.push(`Updated: ${updated.join(", ")}`);
17526
+ return {
17527
+ content: [{ type: "text", text: parts.join("\n") }]
17528
+ };
17529
+ }
17530
+ ),
17531
+ // --- Local → Jira tools ---
17532
+ tool19(
17533
+ "push_artifact_to_jira",
17534
+ "Create a Jira issue from any Marvin artifact (D/A/Q/F/E) and create a tracking JI-xxx document",
17535
+ {
17536
+ artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'D-001', 'F-003', 'E-002')"),
17537
+ projectKey: external_exports.string().describe("Jira project key (e.g. 'PROJ')"),
17538
+ issueType: external_exports.enum(["Story", "Task", "Bug", "Epic"]).optional().describe("Jira issue type (default: 'Task')")
17539
+ },
17540
+ async (args) => {
17541
+ const client = createJiraClient();
17542
+ if (!client) return jiraNotConfiguredError();
17543
+ const artifact = store.get(args.artifactId);
17544
+ if (!artifact) {
17545
+ return {
17546
+ content: [
17547
+ { type: "text", text: `Artifact ${args.artifactId} not found` }
17548
+ ],
17549
+ isError: true
17550
+ };
17551
+ }
17552
+ const description = [
17553
+ artifact.content,
17554
+ "",
17555
+ `---`,
17556
+ `Marvin artifact: ${artifact.frontmatter.id} (${artifact.frontmatter.type})`,
17557
+ `Status: ${artifact.frontmatter.status}`
17558
+ ].join("\n");
17559
+ const jiraResult = await client.createIssue({
17560
+ project: { key: args.projectKey },
17561
+ summary: artifact.frontmatter.title,
17562
+ description,
17563
+ issuetype: { name: args.issueType ?? "Task" }
17564
+ });
17565
+ const host = process.env.JIRA_HOST;
17566
+ const jiDoc = store.create(
17567
+ JIRA_TYPE,
17568
+ {
17569
+ title: artifact.frontmatter.title,
17570
+ status: "open",
17571
+ jiraKey: jiraResult.key,
17572
+ jiraUrl: `https://${host}/browse/${jiraResult.key}`,
17573
+ issueType: args.issueType ?? "Task",
17574
+ priority: "Medium",
17575
+ assignee: "",
17576
+ labels: [],
17577
+ linkedArtifacts: [args.artifactId],
17578
+ tags: [`jira:${jiraResult.key}`],
17579
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
17580
+ },
17581
+ ""
17582
+ );
17583
+ return {
17584
+ content: [
17585
+ {
17586
+ type: "text",
17587
+ text: `Created Jira ${jiraResult.key} from ${args.artifactId}. Tracking locally as ${jiDoc.frontmatter.id}.`
17588
+ }
17589
+ ]
17590
+ };
17591
+ }
17592
+ ),
17593
+ // --- Bidirectional sync ---
17594
+ tool19(
17595
+ "sync_jira_issue",
17596
+ "Bidirectional sync: push local title/description to Jira, pull latest status/assignee/labels back",
17597
+ {
17598
+ id: external_exports.string().describe("Local JI-xxx ID")
17599
+ },
17600
+ async (args) => {
17601
+ const client = createJiraClient();
17602
+ if (!client) return jiraNotConfiguredError();
17603
+ const doc = store.get(args.id);
17604
+ if (!doc || doc.frontmatter.type !== JIRA_TYPE) {
17605
+ return {
17606
+ content: [
17607
+ { type: "text", text: `Jira issue ${args.id} not found locally` }
17608
+ ],
17609
+ isError: true
17610
+ };
17611
+ }
17612
+ const jiraKey = doc.frontmatter.jiraKey;
17613
+ await client.updateIssue(jiraKey, {
17614
+ summary: doc.frontmatter.title,
17615
+ description: doc.content || void 0
17616
+ });
17617
+ const issue2 = await client.getIssue(jiraKey);
17618
+ const host = process.env.JIRA_HOST;
17619
+ const fm = jiraIssueToFrontmatter(
17620
+ issue2,
17621
+ host,
17622
+ doc.frontmatter.linkedArtifacts
17623
+ );
17624
+ store.update(args.id, fm, issue2.fields.description ?? "");
17625
+ return {
17626
+ content: [
17627
+ {
17628
+ type: "text",
17629
+ text: `Synced ${args.id} \u2194 ${jiraKey}. Status: ${fm.status}, Assignee: ${fm.assignee || "unassigned"}`
17630
+ }
17631
+ ]
17632
+ };
17633
+ }
17634
+ ),
17635
+ // --- Local link tool ---
17636
+ tool19(
17637
+ "link_artifact_to_jira",
17638
+ "Add a Marvin artifact ID to a JI-xxx document's linkedArtifacts field",
17639
+ {
17640
+ jiraIssueId: external_exports.string().describe("Local JI-xxx ID"),
17641
+ artifactId: external_exports.string().describe("Marvin artifact ID to link (e.g. 'D-001', 'F-003')")
17642
+ },
17643
+ async (args) => {
17644
+ const doc = store.get(args.jiraIssueId);
17645
+ if (!doc || doc.frontmatter.type !== JIRA_TYPE) {
17646
+ return {
17647
+ content: [
17648
+ {
17649
+ type: "text",
17650
+ text: `Jira issue ${args.jiraIssueId} not found locally`
17651
+ }
17652
+ ],
17653
+ isError: true
17654
+ };
17655
+ }
17656
+ const artifact = store.get(args.artifactId);
17657
+ if (!artifact) {
17658
+ return {
17659
+ content: [
17660
+ { type: "text", text: `Artifact ${args.artifactId} not found` }
17661
+ ],
17662
+ isError: true
17663
+ };
17664
+ }
17665
+ const linked = doc.frontmatter.linkedArtifacts ?? [];
17666
+ if (linked.includes(args.artifactId)) {
17667
+ return {
17668
+ content: [
17669
+ {
17670
+ type: "text",
17671
+ text: `${args.artifactId} is already linked to ${args.jiraIssueId}`
17672
+ }
17673
+ ]
17674
+ };
17675
+ }
17676
+ store.update(args.jiraIssueId, {
17677
+ linkedArtifacts: [...linked, args.artifactId]
17678
+ });
17679
+ return {
17680
+ content: [
17681
+ {
17682
+ type: "text",
17683
+ text: `Linked ${args.artifactId} to ${args.jiraIssueId}`
17684
+ }
17685
+ ]
17686
+ };
17687
+ }
17688
+ )
17689
+ ];
17690
+ }
17691
+
17692
+ // src/skills/builtin/jira/index.ts
17693
+ var jiraSkill = {
17694
+ id: "jira",
17695
+ name: "Jira Integration",
17696
+ description: "Bidirectional sync between Marvin artifacts and Jira issues",
17697
+ version: "1.0.0",
17698
+ format: "builtin-ts",
17699
+ // No default persona affinity — opt-in via config.yaml skills section
17700
+ documentTypeRegistrations: [
17701
+ { type: "jira-issue", dirName: "jira-issues", idPrefix: "JI" }
17702
+ ],
17703
+ tools: (store) => createJiraTools(store),
17704
+ promptFragments: {
17705
+ "product-owner": `You have the **Jira Integration** skill. You can pull issues from Jira and push Marvin artifacts to Jira.
17706
+
17707
+ **Available tools:**
17708
+ - \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues
17709
+ - \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
17710
+ - \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, feature, etc.)
17711
+ - \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
17712
+ - \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx
17713
+
17714
+ **As Product Owner, use Jira integration to:**
17715
+ - Pull stakeholder-reported issues for triage and prioritization
17716
+ - Push approved features as Stories for development tracking
17717
+ - Link decisions to Jira issues for audit trail and traceability
17718
+ - Use JQL queries to review backlog status (e.g. \`project = PROJ AND status = "To Do"\`)`,
17719
+ "tech-lead": `You have the **Jira Integration** skill. You can pull issues from Jira and push Marvin artifacts to Jira.
17720
+
17721
+ **Available tools:**
17722
+ - \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues
17723
+ - \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
17724
+ - \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, action, epic, etc.)
17725
+ - \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
17726
+ - \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx
17727
+
17728
+ **As Tech Lead, use Jira integration to:**
17729
+ - Pull technical issues and bugs for sprint planning and estimation
17730
+ - Push epics and technical decisions to Jira for cross-team visibility
17731
+ - Bidirectional sync to keep local governance and Jira in alignment
17732
+ - Use JQL queries to track technical debt (e.g. \`labels = "tech-debt" AND status != "Done"\`)`,
17733
+ "delivery-manager": `You have the **Jira Integration** skill. You can pull issues from Jira and push Marvin artifacts to Jira.
17734
+
17735
+ **Available tools:**
17736
+ - \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues
17737
+ - \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
17738
+ - \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, action, etc.)
17739
+ - \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
17740
+ - \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx
17741
+
17742
+ **As Delivery Manager, use Jira integration to:**
17743
+ - Pull sprint issues for tracking progress and blockers
17744
+ - Push actions and decisions to Jira for stakeholder visibility
17745
+ - Use JQL queries for reporting (e.g. \`sprint in openSprints() AND assignee = currentUser()\`)
17746
+ - Sync status between Marvin governance items and Jira issues`
17747
+ }
17748
+ };
17749
+
16980
17750
  // src/skills/registry.ts
16981
17751
  var BUILTIN_SKILLS = {
16982
- "governance-review": governanceReviewSkill
17752
+ "governance-review": governanceReviewSkill,
17753
+ "jira": jiraSkill
16983
17754
  };
16984
17755
  function getBuiltinSkillsDir() {
16985
17756
  const thisFile = fileURLToPath(import.meta.url);
@@ -17136,7 +17907,7 @@ ${fragment}`);
17136
17907
  }
17137
17908
 
17138
17909
  // src/skills/action-tools.ts
17139
- import { tool as tool18 } from "@anthropic-ai/claude-agent-sdk";
17910
+ import { tool as tool20 } from "@anthropic-ai/claude-agent-sdk";
17140
17911
 
17141
17912
  // src/skills/action-runner.ts
17142
17913
  import { query } from "@anthropic-ai/claude-agent-sdk";
@@ -17226,7 +17997,7 @@ function createSkillActionTools(skills, context) {
17226
17997
  if (!skill.actions) continue;
17227
17998
  for (const action of skill.actions) {
17228
17999
  tools.push(
17229
- tool18(
18000
+ tool20(
17230
18001
  `${skill.id}__${action.id}`,
17231
18002
  action.description,
17232
18003
  {
@@ -17453,10 +18224,10 @@ ${lines.join("\n\n")}`;
17453
18224
  }
17454
18225
 
17455
18226
  // src/mcp/persona-tools.ts
17456
- import { tool as tool19 } from "@anthropic-ai/claude-agent-sdk";
18227
+ import { tool as tool21 } from "@anthropic-ai/claude-agent-sdk";
17457
18228
  function createPersonaTools(ctx, marvinDir) {
17458
18229
  return [
17459
- tool19(
18230
+ tool21(
17460
18231
  "set_persona",
17461
18232
  "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.",
17462
18233
  {
@@ -17486,7 +18257,7 @@ ${summaries}`
17486
18257
  };
17487
18258
  }
17488
18259
  ),
17489
- tool19(
18260
+ tool21(
17490
18261
  "get_persona_guidance",
17491
18262
  "Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
17492
18263
  {