mrvn-cli 0.4.6 → 0.4.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.
@@ -176,9 +176,9 @@ var DocumentStore = class {
176
176
  }
177
177
  }
178
178
  }
179
- list(query2) {
179
+ list(query3) {
180
180
  const results = [];
181
- const types = query2?.type ? [query2.type] : Object.keys(this.typeDirs);
181
+ const types = query3?.type ? [query3.type] : Object.keys(this.typeDirs);
182
182
  for (const type of types) {
183
183
  const dirName = this.typeDirs[type];
184
184
  if (!dirName) continue;
@@ -189,9 +189,9 @@ var DocumentStore = class {
189
189
  const filePath = path3.join(dir, file2);
190
190
  const raw = fs3.readFileSync(filePath, "utf-8");
191
191
  const doc = parseDocument(raw, filePath);
192
- if (query2?.status && doc.frontmatter.status !== query2.status) continue;
193
- if (query2?.owner && doc.frontmatter.owner !== query2.owner) continue;
194
- if (query2?.tag && (!doc.frontmatter.tags || !doc.frontmatter.tags.includes(query2.tag)))
192
+ if (query3?.status && doc.frontmatter.status !== query3.status) continue;
193
+ if (query3?.owner && doc.frontmatter.owner !== query3.owner) continue;
194
+ if (query3?.tag && (!doc.frontmatter.tags || !doc.frontmatter.tags.includes(query3.tag)))
195
195
  continue;
196
196
  results.push(doc);
197
197
  }
@@ -15482,6 +15482,385 @@ function evaluateHealth(projectName, metrics) {
15482
15482
  };
15483
15483
  }
15484
15484
 
15485
+ // src/plugins/builtin/tools/task-utils.ts
15486
+ function normalizeLinkedEpics(value) {
15487
+ if (value === void 0 || value === null) return [];
15488
+ if (typeof value === "string") {
15489
+ try {
15490
+ const parsed = JSON.parse(value);
15491
+ if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
15492
+ } catch {
15493
+ }
15494
+ return [value];
15495
+ }
15496
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
15497
+ return [];
15498
+ }
15499
+ function generateEpicTags(epics) {
15500
+ return epics.map((id) => `epic:${id}`);
15501
+ }
15502
+
15503
+ // src/reports/sprint-summary/collector.ts
15504
+ var DONE_STATUSES = /* @__PURE__ */ new Set(["done", "closed", "resolved", "cancelled"]);
15505
+ function collectSprintSummaryData(store, sprintId) {
15506
+ const allDocs = store.list();
15507
+ const sprintDocs = allDocs.filter((d) => d.frontmatter.type === "sprint");
15508
+ let sprintDoc;
15509
+ if (sprintId) {
15510
+ sprintDoc = sprintDocs.find((d) => d.frontmatter.id === sprintId);
15511
+ } else {
15512
+ sprintDoc = sprintDocs.find((d) => d.frontmatter.status === "active");
15513
+ }
15514
+ if (!sprintDoc) return null;
15515
+ const fm = sprintDoc.frontmatter;
15516
+ const startDate = fm.startDate;
15517
+ const endDate = fm.endDate;
15518
+ const today = /* @__PURE__ */ new Date();
15519
+ const todayStr = today.toISOString().slice(0, 10);
15520
+ let daysElapsed = 0;
15521
+ let daysRemaining = 0;
15522
+ let totalDays = 0;
15523
+ let percentComplete = 0;
15524
+ if (startDate && endDate) {
15525
+ const startMs = new Date(startDate).getTime();
15526
+ const endMs = new Date(endDate).getTime();
15527
+ const todayMs = today.getTime();
15528
+ const msPerDay = 864e5;
15529
+ totalDays = Math.max(1, Math.round((endMs - startMs) / msPerDay));
15530
+ daysElapsed = Math.max(0, Math.round((todayMs - startMs) / msPerDay));
15531
+ daysRemaining = Math.max(0, Math.round((endMs - todayMs) / msPerDay));
15532
+ percentComplete = Math.min(100, Math.round(daysElapsed / totalDays * 100));
15533
+ }
15534
+ const linkedEpicIds = normalizeLinkedEpics(fm.linkedEpics);
15535
+ const epicToTasks = /* @__PURE__ */ new Map();
15536
+ const allTasks = allDocs.filter((d) => d.frontmatter.type === "task");
15537
+ for (const task of allTasks) {
15538
+ const tags = task.frontmatter.tags ?? [];
15539
+ for (const tag of tags) {
15540
+ if (tag.startsWith("epic:")) {
15541
+ const epicId = tag.slice(5);
15542
+ if (!epicToTasks.has(epicId)) epicToTasks.set(epicId, []);
15543
+ epicToTasks.get(epicId).push(task);
15544
+ }
15545
+ }
15546
+ }
15547
+ const linkedEpics = linkedEpicIds.map((epicId) => {
15548
+ const epic = store.get(epicId);
15549
+ const tasks = epicToTasks.get(epicId) ?? [];
15550
+ const tasksDone = tasks.filter((t) => DONE_STATUSES.has(t.frontmatter.status)).length;
15551
+ return {
15552
+ id: epicId,
15553
+ title: epic?.frontmatter.title ?? "(not found)",
15554
+ status: epic?.frontmatter.status ?? "unknown",
15555
+ tasksDone,
15556
+ tasksTotal: tasks.length
15557
+ };
15558
+ });
15559
+ const sprintTag = `sprint:${fm.id}`;
15560
+ const workItemDocs = allDocs.filter(
15561
+ (d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.tags?.includes(sprintTag)
15562
+ );
15563
+ const primaryDocs = workItemDocs.filter((d) => d.frontmatter.type !== "contribution");
15564
+ const byStatus = {};
15565
+ const byType = {};
15566
+ let doneCount = 0;
15567
+ let inProgressCount = 0;
15568
+ let openCount = 0;
15569
+ let blockedCount = 0;
15570
+ for (const doc of primaryDocs) {
15571
+ const s = doc.frontmatter.status;
15572
+ byStatus[s] = (byStatus[s] ?? 0) + 1;
15573
+ byType[doc.frontmatter.type] = (byType[doc.frontmatter.type] ?? 0) + 1;
15574
+ if (DONE_STATUSES.has(s)) doneCount++;
15575
+ else if (s === "in-progress") inProgressCount++;
15576
+ else if (s === "blocked") blockedCount++;
15577
+ else openCount++;
15578
+ }
15579
+ const allItemsById = /* @__PURE__ */ new Map();
15580
+ const childrenByParent = /* @__PURE__ */ new Map();
15581
+ const sprintItemIds = new Set(workItemDocs.map((d) => d.frontmatter.id));
15582
+ for (const doc of workItemDocs) {
15583
+ const about = doc.frontmatter.aboutArtifact;
15584
+ const item = {
15585
+ id: doc.frontmatter.id,
15586
+ title: doc.frontmatter.title,
15587
+ type: doc.frontmatter.type,
15588
+ status: doc.frontmatter.status,
15589
+ aboutArtifact: about
15590
+ };
15591
+ allItemsById.set(item.id, item);
15592
+ if (about && sprintItemIds.has(about)) {
15593
+ if (!childrenByParent.has(about)) childrenByParent.set(about, []);
15594
+ childrenByParent.get(about).push(item);
15595
+ }
15596
+ }
15597
+ const itemsWithChildren = /* @__PURE__ */ new Set();
15598
+ for (const [parentId, children] of childrenByParent) {
15599
+ const parent = allItemsById.get(parentId);
15600
+ if (parent) {
15601
+ parent.children = children;
15602
+ for (const child of children) itemsWithChildren.add(child.id);
15603
+ }
15604
+ }
15605
+ for (const item of allItemsById.values()) {
15606
+ if (item.children) {
15607
+ for (const child of item.children) {
15608
+ const grandchildren = childrenByParent.get(child.id);
15609
+ if (grandchildren) {
15610
+ child.children = grandchildren;
15611
+ for (const gc of grandchildren) itemsWithChildren.add(gc.id);
15612
+ }
15613
+ }
15614
+ }
15615
+ }
15616
+ const items = [];
15617
+ for (const doc of workItemDocs) {
15618
+ if (!itemsWithChildren.has(doc.frontmatter.id)) {
15619
+ items.push(allItemsById.get(doc.frontmatter.id));
15620
+ }
15621
+ }
15622
+ const workItems = {
15623
+ total: primaryDocs.length,
15624
+ done: doneCount,
15625
+ inProgress: inProgressCount,
15626
+ open: openCount,
15627
+ blocked: blockedCount,
15628
+ completionPct: primaryDocs.length > 0 ? Math.round(doneCount / primaryDocs.length * 100) : 0,
15629
+ byStatus,
15630
+ byType,
15631
+ items
15632
+ };
15633
+ const meetings = [];
15634
+ if (startDate && endDate) {
15635
+ const meetingDocs = allDocs.filter((d) => d.frontmatter.type === "meeting");
15636
+ for (const m of meetingDocs) {
15637
+ const meetingDate = m.frontmatter.date ?? m.frontmatter.created.slice(0, 10);
15638
+ if (meetingDate >= startDate && meetingDate <= endDate) {
15639
+ meetings.push({
15640
+ id: m.frontmatter.id,
15641
+ title: m.frontmatter.title,
15642
+ date: meetingDate
15643
+ });
15644
+ }
15645
+ }
15646
+ meetings.sort((a, b) => a.date.localeCompare(b.date));
15647
+ }
15648
+ const artifacts = [];
15649
+ if (startDate && endDate) {
15650
+ for (const doc of allDocs) {
15651
+ if (doc.frontmatter.type === "sprint") continue;
15652
+ const created = doc.frontmatter.created.slice(0, 10);
15653
+ const updated = doc.frontmatter.updated.slice(0, 10);
15654
+ if (created >= startDate && created <= endDate) {
15655
+ artifacts.push({
15656
+ id: doc.frontmatter.id,
15657
+ title: doc.frontmatter.title,
15658
+ type: doc.frontmatter.type,
15659
+ action: "created",
15660
+ date: created
15661
+ });
15662
+ } else if (updated >= startDate && updated <= endDate && updated !== created) {
15663
+ artifacts.push({
15664
+ id: doc.frontmatter.id,
15665
+ title: doc.frontmatter.title,
15666
+ type: doc.frontmatter.type,
15667
+ action: "updated",
15668
+ date: updated
15669
+ });
15670
+ }
15671
+ }
15672
+ artifacts.sort((a, b) => b.date.localeCompare(a.date));
15673
+ }
15674
+ const relevantTags = /* @__PURE__ */ new Set([sprintTag, ...linkedEpicIds.map((id) => `epic:${id}`)]);
15675
+ const openActions = allDocs.filter(
15676
+ (d) => d.frontmatter.type === "action" && !DONE_STATUSES.has(d.frontmatter.status) && d.frontmatter.tags?.some((t) => relevantTags.has(t))
15677
+ ).map((d) => ({
15678
+ id: d.frontmatter.id,
15679
+ title: d.frontmatter.title,
15680
+ owner: d.frontmatter.owner,
15681
+ dueDate: d.frontmatter.dueDate
15682
+ }));
15683
+ const openQuestions = allDocs.filter(
15684
+ (d) => d.frontmatter.type === "question" && d.frontmatter.status === "open" && d.frontmatter.tags?.some((t) => relevantTags.has(t))
15685
+ ).map((d) => ({
15686
+ id: d.frontmatter.id,
15687
+ title: d.frontmatter.title
15688
+ }));
15689
+ const blockers = allDocs.filter(
15690
+ (d) => d.frontmatter.status === "blocked" && d.frontmatter.tags?.includes(sprintTag)
15691
+ ).map((d) => ({
15692
+ id: d.frontmatter.id,
15693
+ title: d.frontmatter.title,
15694
+ type: d.frontmatter.type
15695
+ }));
15696
+ const riskBlockers = allDocs.filter(
15697
+ (d) => !DONE_STATUSES.has(d.frontmatter.status) && d.frontmatter.tags?.includes("risk") && d.frontmatter.tags?.some((t) => relevantTags.has(t)) && !blockers.some((b) => b.id === d.frontmatter.id)
15698
+ );
15699
+ for (const d of riskBlockers) {
15700
+ blockers.push({
15701
+ id: d.frontmatter.id,
15702
+ title: d.frontmatter.title,
15703
+ type: d.frontmatter.type
15704
+ });
15705
+ }
15706
+ let velocity = null;
15707
+ const currentRate = workItems.completionPct;
15708
+ const completedSprints = sprintDocs.filter((s) => DONE_STATUSES.has(s.frontmatter.status) && s.frontmatter.id !== fm.id).sort((a, b) => (b.frontmatter.endDate ?? "").localeCompare(a.frontmatter.endDate ?? ""));
15709
+ if (completedSprints.length > 0) {
15710
+ const prev = completedSprints[0];
15711
+ const prevTag = `sprint:${prev.frontmatter.id}`;
15712
+ const prevWorkItems = allDocs.filter(
15713
+ (d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.type !== "contribution" && d.frontmatter.tags?.includes(prevTag)
15714
+ );
15715
+ const prevDone = prevWorkItems.filter((d) => DONE_STATUSES.has(d.frontmatter.status)).length;
15716
+ const prevRate = prevWorkItems.length > 0 ? Math.round(prevDone / prevWorkItems.length * 100) : 0;
15717
+ velocity = {
15718
+ currentCompletionRate: currentRate,
15719
+ previousSprintRate: prevRate,
15720
+ previousSprintId: prev.frontmatter.id
15721
+ };
15722
+ } else {
15723
+ velocity = { currentCompletionRate: currentRate };
15724
+ }
15725
+ return {
15726
+ sprint: {
15727
+ id: fm.id,
15728
+ title: fm.title,
15729
+ goal: fm.goal,
15730
+ status: fm.status,
15731
+ startDate,
15732
+ endDate
15733
+ },
15734
+ timeline: { daysElapsed, daysRemaining, totalDays, percentComplete },
15735
+ linkedEpics,
15736
+ workItems,
15737
+ meetings,
15738
+ artifacts,
15739
+ openActions,
15740
+ openQuestions,
15741
+ blockers,
15742
+ velocity
15743
+ };
15744
+ }
15745
+
15746
+ // src/reports/sprint-summary/generator.ts
15747
+ import { query } from "@anthropic-ai/claude-agent-sdk";
15748
+ async function generateSprintSummary(data) {
15749
+ const prompt = buildPrompt(data);
15750
+ const result = query({
15751
+ prompt,
15752
+ options: {
15753
+ systemPrompt: SYSTEM_PROMPT,
15754
+ maxTurns: 1,
15755
+ tools: [],
15756
+ allowedTools: []
15757
+ }
15758
+ });
15759
+ for await (const msg of result) {
15760
+ if (msg.type === "assistant") {
15761
+ const text = msg.message.content.find(
15762
+ (b) => b.type === "text"
15763
+ );
15764
+ if (text) return text.text;
15765
+ }
15766
+ }
15767
+ return "Unable to generate sprint summary.";
15768
+ }
15769
+ var SYSTEM_PROMPT = `You are a delivery management assistant generating a sprint summary narrative. Produce a concise, insightful markdown report. Do NOT include a top-level heading \u2014 the caller will add one. Use the following structure:
15770
+
15771
+ ## Sprint Health
15772
+ One-line verdict on overall sprint health (healthy / at risk / behind).
15773
+
15774
+ ## Goal Progress
15775
+ How close the team is to achieving the sprint goal. Reference the goal text and completion metrics.
15776
+
15777
+ ## Key Achievements
15778
+ Notable completions, decisions made, meetings held during the sprint. Use bullet points.
15779
+
15780
+ ## Current Risks
15781
+ Blockers, overdue items, unresolved questions, items without owners. Use bullet points. If none, say so.
15782
+
15783
+ ## Outcome Projection
15784
+ Given the current pace and time remaining, what's the likely outcome? Will the sprint goal be met?
15785
+
15786
+ Be specific \u2014 reference artifact IDs, dates, and numbers from the data. Keep the tone professional but direct.`;
15787
+ function buildPrompt(data) {
15788
+ const sections = [];
15789
+ sections.push(`# Sprint: ${data.sprint.id} \u2014 ${data.sprint.title}`);
15790
+ sections.push(`Status: ${data.sprint.status}`);
15791
+ if (data.sprint.goal) sections.push(`Goal: ${data.sprint.goal}`);
15792
+ if (data.sprint.startDate) sections.push(`Start: ${data.sprint.startDate}`);
15793
+ if (data.sprint.endDate) sections.push(`End: ${data.sprint.endDate}`);
15794
+ sections.push(`
15795
+ ## Timeline`);
15796
+ sections.push(`Days elapsed: ${data.timeline.daysElapsed} / ${data.timeline.totalDays}`);
15797
+ sections.push(`Days remaining: ${data.timeline.daysRemaining}`);
15798
+ sections.push(`Timeline progress: ${data.timeline.percentComplete}%`);
15799
+ sections.push(`
15800
+ ## Work Items`);
15801
+ sections.push(`Total: ${data.workItems.total}, Done: ${data.workItems.done}, In Progress: ${data.workItems.inProgress}, Open: ${data.workItems.open}, Blocked: ${data.workItems.blocked}`);
15802
+ sections.push(`Completion: ${data.workItems.completionPct}%`);
15803
+ if (Object.keys(data.workItems.byType).length > 0) {
15804
+ sections.push(`By type: ${Object.entries(data.workItems.byType).map(([t, n]) => `${t}: ${n}`).join(", ")}`);
15805
+ }
15806
+ if (data.linkedEpics.length > 0) {
15807
+ sections.push(`
15808
+ ## Linked Epics`);
15809
+ for (const e of data.linkedEpics) {
15810
+ sections.push(`- ${e.id}: ${e.title} [${e.status}] \u2014 ${e.tasksDone}/${e.tasksTotal} tasks done`);
15811
+ }
15812
+ }
15813
+ if (data.meetings.length > 0) {
15814
+ sections.push(`
15815
+ ## Meetings During Sprint`);
15816
+ for (const m of data.meetings) {
15817
+ sections.push(`- ${m.date}: ${m.id} \u2014 ${m.title}`);
15818
+ }
15819
+ }
15820
+ if (data.artifacts.length > 0) {
15821
+ sections.push(`
15822
+ ## Artifacts Created/Updated During Sprint`);
15823
+ for (const a of data.artifacts.slice(0, 20)) {
15824
+ sections.push(`- ${a.date}: ${a.id} (${a.type}) ${a.action} \u2014 ${a.title}`);
15825
+ }
15826
+ if (data.artifacts.length > 20) {
15827
+ sections.push(`... and ${data.artifacts.length - 20} more`);
15828
+ }
15829
+ }
15830
+ if (data.openActions.length > 0) {
15831
+ sections.push(`
15832
+ ## Open Actions`);
15833
+ for (const a of data.openActions) {
15834
+ const owner = a.owner ?? "unowned";
15835
+ const due = a.dueDate ?? "no due date";
15836
+ sections.push(`- ${a.id}: ${a.title} (${owner}, ${due})`);
15837
+ }
15838
+ }
15839
+ if (data.openQuestions.length > 0) {
15840
+ sections.push(`
15841
+ ## Open Questions`);
15842
+ for (const q of data.openQuestions) {
15843
+ sections.push(`- ${q.id}: ${q.title}`);
15844
+ }
15845
+ }
15846
+ if (data.blockers.length > 0) {
15847
+ sections.push(`
15848
+ ## Blockers`);
15849
+ for (const b of data.blockers) {
15850
+ sections.push(`- ${b.id} (${b.type}): ${b.title}`);
15851
+ }
15852
+ }
15853
+ if (data.velocity) {
15854
+ sections.push(`
15855
+ ## Velocity`);
15856
+ sections.push(`Current sprint completion rate: ${data.velocity.currentCompletionRate}%`);
15857
+ if (data.velocity.previousSprintRate !== void 0) {
15858
+ sections.push(`Previous sprint (${data.velocity.previousSprintId}): ${data.velocity.previousSprintRate}%`);
15859
+ }
15860
+ }
15861
+ return sections.join("\n");
15862
+ }
15863
+
15485
15864
  // src/plugins/builtin/tools/reports.ts
15486
15865
  function createReportTools(store) {
15487
15866
  return [
@@ -15773,6 +16152,25 @@ function createReportTools(store) {
15773
16152
  },
15774
16153
  { annotations: { readOnlyHint: true } }
15775
16154
  ),
16155
+ tool8(
16156
+ "generate_sprint_summary",
16157
+ "Generate an AI-powered narrative summary of a sprint's progress, health, achievements, risks, and projected outcome",
16158
+ {
16159
+ sprint: external_exports.string().optional().describe("Sprint ID (e.g. 'SP-001'). Omit for the active sprint.")
16160
+ },
16161
+ async (args) => {
16162
+ const data = collectSprintSummaryData(store, args.sprint);
16163
+ if (!data) {
16164
+ const msg = args.sprint ? `Sprint ${args.sprint} not found.` : "No active sprint found.";
16165
+ return { content: [{ type: "text", text: msg }], isError: true };
16166
+ }
16167
+ const summary = await generateSprintSummary(data);
16168
+ return {
16169
+ content: [{ type: "text", text: summary }]
16170
+ };
16171
+ },
16172
+ { annotations: { readOnlyHint: true } }
16173
+ ),
15776
16174
  tool8(
15777
16175
  "save_report",
15778
16176
  "Save a generated report as a persistent document",
@@ -16205,18 +16603,18 @@ function createContributionTools(store) {
16205
16603
  content: external_exports.string().describe("Contribution content \u2014 the input from the persona"),
16206
16604
  persona: external_exports.string().describe("Persona making the contribution (e.g. 'tech-lead')"),
16207
16605
  contributionType: external_exports.string().describe("Type of contribution (e.g. 'action-result', 'risk-finding')"),
16208
- aboutArtifact: external_exports.string().optional().describe("Artifact ID this contribution relates to (e.g. 'A-001')"),
16209
- status: external_exports.string().optional().describe("Status (default: 'open')"),
16606
+ aboutArtifact: external_exports.string().describe("Artifact ID this contribution relates to (e.g. 'A-001', 'T-003')"),
16607
+ status: external_exports.string().optional().describe("Status (default: 'done')"),
16210
16608
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
16211
16609
  },
16212
16610
  async (args) => {
16213
16611
  const frontmatter = {
16214
16612
  title: args.title,
16215
- status: args.status ?? "open",
16613
+ status: args.status ?? "done",
16216
16614
  persona: args.persona,
16217
16615
  contributionType: args.contributionType
16218
16616
  };
16219
- if (args.aboutArtifact) frontmatter.aboutArtifact = args.aboutArtifact;
16617
+ frontmatter.aboutArtifact = args.aboutArtifact;
16220
16618
  if (args.tags) frontmatter.tags = args.tags;
16221
16619
  const doc = store.create("contribution", frontmatter, args.content);
16222
16620
  return {
@@ -16616,26 +17014,6 @@ function createSprintPlanningTools(store) {
16616
17014
 
16617
17015
  // src/plugins/builtin/tools/tasks.ts
16618
17016
  import { tool as tool14 } from "@anthropic-ai/claude-agent-sdk";
16619
-
16620
- // src/plugins/builtin/tools/task-utils.ts
16621
- function normalizeLinkedEpics(value) {
16622
- if (value === void 0 || value === null) return [];
16623
- if (typeof value === "string") {
16624
- try {
16625
- const parsed = JSON.parse(value);
16626
- if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
16627
- } catch {
16628
- }
16629
- return [value];
16630
- }
16631
- if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
16632
- return [];
16633
- }
16634
- function generateEpicTags(epics) {
16635
- return epics.map((id) => `epic:${id}`);
16636
- }
16637
-
16638
- // src/plugins/builtin/tools/tasks.ts
16639
17017
  var linkedEpicArray = external_exports.preprocess(
16640
17018
  (val) => {
16641
17019
  if (typeof val === "string") {
@@ -16720,6 +17098,7 @@ function createTaskTools(store) {
16720
17098
  title: external_exports.string().describe("Task title"),
16721
17099
  content: external_exports.string().describe("Task description and implementation details"),
16722
17100
  linkedEpic: linkedEpicArray.describe("Epic ID(s) to link this task to (e.g. ['E-001'] or ['E-001', 'E-002'])"),
17101
+ aboutArtifact: external_exports.string().optional().describe("Parent artifact this task derives from (e.g. 'A-001')"),
16723
17102
  status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("Task status (default: 'backlog')"),
16724
17103
  acceptanceCriteria: external_exports.string().optional().describe("Acceptance criteria for the task"),
16725
17104
  technicalNotes: external_exports.string().optional().describe("Technical implementation notes"),
@@ -16745,6 +17124,7 @@ function createTaskTools(store) {
16745
17124
  linkedEpic: linkedEpics,
16746
17125
  tags: [...generateEpicTags(linkedEpics), ...args.tags ?? []]
16747
17126
  };
17127
+ if (args.aboutArtifact) frontmatter.aboutArtifact = args.aboutArtifact;
16748
17128
  if (args.acceptanceCriteria) frontmatter.acceptanceCriteria = args.acceptanceCriteria;
16749
17129
  if (args.technicalNotes) frontmatter.technicalNotes = args.technicalNotes;
16750
17130
  if (args.estimatedPoints !== void 0) frontmatter.estimatedPoints = args.estimatedPoints;
@@ -16768,6 +17148,7 @@ function createTaskTools(store) {
16768
17148
  {
16769
17149
  id: external_exports.string().describe("Task ID to update"),
16770
17150
  title: external_exports.string().optional().describe("New title"),
17151
+ aboutArtifact: external_exports.string().optional().describe("Parent artifact this task derives from (e.g. 'A-001')"),
16771
17152
  status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("New status"),
16772
17153
  content: external_exports.string().optional().describe("New content"),
16773
17154
  linkedEpic: linkedEpicArray.optional().describe("New linked epic ID(s)"),
@@ -19020,7 +19401,7 @@ ${fragment}`);
19020
19401
  import { tool as tool23 } from "@anthropic-ai/claude-agent-sdk";
19021
19402
 
19022
19403
  // src/skills/action-runner.ts
19023
- import { query } from "@anthropic-ai/claude-agent-sdk";
19404
+ import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
19024
19405
 
19025
19406
  // src/agent/mcp-server.ts
19026
19407
  import {
@@ -19152,7 +19533,7 @@ function computeUrgency(dueDateStr, todayStr) {
19152
19533
  if (diffDays <= 14) return "upcoming";
19153
19534
  return "later";
19154
19535
  }
19155
- var DONE_STATUSES = /* @__PURE__ */ new Set(["done", "closed", "resolved", "cancelled"]);
19536
+ var DONE_STATUSES2 = /* @__PURE__ */ new Set(["done", "closed", "resolved", "cancelled"]);
19156
19537
  function getUpcomingData(store) {
19157
19538
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19158
19539
  const allDocs = store.list();
@@ -19161,7 +19542,7 @@ function getUpcomingData(store) {
19161
19542
  docById.set(doc.frontmatter.id, doc);
19162
19543
  }
19163
19544
  const actions = allDocs.filter(
19164
- (d) => d.frontmatter.type === "action" && !DONE_STATUSES.has(d.frontmatter.status)
19545
+ (d) => d.frontmatter.type === "action" && !DONE_STATUSES2.has(d.frontmatter.status)
19165
19546
  );
19166
19547
  const actionsWithDue = actions.filter((d) => d.frontmatter.dueDate);
19167
19548
  const sprints = allDocs.filter((d) => d.frontmatter.type === "sprint");
@@ -19225,7 +19606,7 @@ function getUpcomingData(store) {
19225
19606
  const sprintEnd = sprint.frontmatter.endDate;
19226
19607
  const sprintTaskDocs = getSprintTasks(sprint);
19227
19608
  for (const task of sprintTaskDocs) {
19228
- if (DONE_STATUSES.has(task.frontmatter.status)) continue;
19609
+ if (DONE_STATUSES2.has(task.frontmatter.status)) continue;
19229
19610
  const existing = taskSprintMap.get(task.frontmatter.id);
19230
19611
  if (!existing || sprintEnd < existing.sprintEnd) {
19231
19612
  taskSprintMap.set(task.frontmatter.id, { task, sprint, sprintEnd });
@@ -19242,7 +19623,7 @@ function getUpcomingData(store) {
19242
19623
  urgency: computeUrgency(sprintEnd, today)
19243
19624
  })).sort((a, b) => a.sprintEndDate.localeCompare(b.sprintEndDate));
19244
19625
  const openItems = allDocs.filter(
19245
- (d) => ["action", "question", "task"].includes(d.frontmatter.type) && !DONE_STATUSES.has(d.frontmatter.status)
19626
+ (d) => ["action", "question", "task"].includes(d.frontmatter.type) && !DONE_STATUSES2.has(d.frontmatter.status)
19246
19627
  );
19247
19628
  const fourteenDaysAgo = new Date(todayMs - fourteenDaysMs).toISOString().slice(0, 10);
19248
19629
  const recentMeetings = allDocs.filter(
@@ -19340,6 +19721,9 @@ function getUpcomingData(store) {
19340
19721
  }).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, 15);
19341
19722
  return { dueSoonActions, dueSoonSprintTasks, trending };
19342
19723
  }
19724
+ function getSprintSummaryData(store, sprintId) {
19725
+ return collectSprintSummaryData(store, sprintId);
19726
+ }
19343
19727
 
19344
19728
  // src/web/templates/layout.ts
19345
19729
  function collapsibleSection(sectionId, title, content, opts) {
@@ -19481,6 +19865,7 @@ function layout(opts, body) {
19481
19865
  const topItems = [
19482
19866
  { href: "/", label: "Overview" },
19483
19867
  { href: "/upcoming", label: "Upcoming" },
19868
+ { href: "/sprint-summary", label: "Sprint Summary" },
19484
19869
  { href: "/timeline", label: "Timeline" },
19485
19870
  { href: "/board", label: "Board" },
19486
19871
  { href: "/gar", label: "GAR Report" },
@@ -19864,6 +20249,17 @@ tr:hover td {
19864
20249
  background: var(--bg-hover);
19865
20250
  }
19866
20251
 
20252
+ /* Hierarchical work-item sub-rows */
20253
+ .child-row td {
20254
+ font-size: 0.8125rem;
20255
+ border-bottom-style: dashed;
20256
+ }
20257
+ .contribution-row td {
20258
+ font-size: 0.8125rem;
20259
+ color: var(--text-dim);
20260
+ border-bottom-style: dashed;
20261
+ }
20262
+
19867
20263
  /* GAR */
19868
20264
  .gar-overall {
19869
20265
  text-align: center;
@@ -20491,6 +20887,112 @@ tr:hover td {
20491
20887
 
20492
20888
  .text-dim { color: var(--text-dim); }
20493
20889
 
20890
+ /* Sprint Summary */
20891
+ .sprint-goal {
20892
+ background: var(--bg-card);
20893
+ border: 1px solid var(--border);
20894
+ border-radius: var(--radius);
20895
+ padding: 0.75rem 1rem;
20896
+ margin-bottom: 1rem;
20897
+ font-size: 0.9rem;
20898
+ color: var(--text);
20899
+ }
20900
+
20901
+ .sprint-progress-bar {
20902
+ position: relative;
20903
+ height: 24px;
20904
+ background: var(--bg-card);
20905
+ border: 1px solid var(--border);
20906
+ border-radius: 12px;
20907
+ margin-bottom: 1.25rem;
20908
+ overflow: hidden;
20909
+ }
20910
+
20911
+ .sprint-progress-fill {
20912
+ height: 100%;
20913
+ background: linear-gradient(90deg, var(--accent-dim), var(--accent));
20914
+ border-radius: 12px;
20915
+ transition: width 0.3s ease;
20916
+ }
20917
+
20918
+ .sprint-progress-label {
20919
+ position: absolute;
20920
+ top: 50%;
20921
+ left: 50%;
20922
+ transform: translate(-50%, -50%);
20923
+ font-size: 0.7rem;
20924
+ font-weight: 700;
20925
+ color: var(--text);
20926
+ }
20927
+
20928
+ .sprint-ai-section {
20929
+ margin-top: 2rem;
20930
+ background: var(--bg-card);
20931
+ border: 1px solid var(--border);
20932
+ border-radius: var(--radius);
20933
+ padding: 1.5rem;
20934
+ }
20935
+
20936
+ .sprint-ai-section h3 {
20937
+ font-size: 1rem;
20938
+ font-weight: 600;
20939
+ margin-bottom: 0.5rem;
20940
+ }
20941
+
20942
+ .sprint-generate-btn {
20943
+ background: var(--accent);
20944
+ color: #fff;
20945
+ border: none;
20946
+ border-radius: var(--radius);
20947
+ padding: 0.5rem 1.25rem;
20948
+ font-size: 0.85rem;
20949
+ font-weight: 600;
20950
+ cursor: pointer;
20951
+ margin-top: 0.75rem;
20952
+ transition: background 0.15s;
20953
+ }
20954
+
20955
+ .sprint-generate-btn:hover:not(:disabled) {
20956
+ background: var(--accent-dim);
20957
+ }
20958
+
20959
+ .sprint-generate-btn:disabled {
20960
+ opacity: 0.5;
20961
+ cursor: not-allowed;
20962
+ }
20963
+
20964
+ .sprint-loading {
20965
+ display: flex;
20966
+ align-items: center;
20967
+ gap: 0.75rem;
20968
+ padding: 1rem 0;
20969
+ color: var(--text-dim);
20970
+ font-size: 0.85rem;
20971
+ }
20972
+
20973
+ .sprint-spinner {
20974
+ width: 20px;
20975
+ height: 20px;
20976
+ border: 2px solid var(--border);
20977
+ border-top-color: var(--accent);
20978
+ border-radius: 50%;
20979
+ animation: sprint-spin 0.8s linear infinite;
20980
+ }
20981
+
20982
+ @keyframes sprint-spin {
20983
+ to { transform: rotate(360deg); }
20984
+ }
20985
+
20986
+ .sprint-error {
20987
+ color: var(--red);
20988
+ font-size: 0.85rem;
20989
+ padding: 0.5rem 0;
20990
+ }
20991
+
20992
+ .sprint-ai-section .detail-content {
20993
+ margin-top: 1rem;
20994
+ }
20995
+
20494
20996
  /* Collapsible sections */
20495
20997
  .collapsible-header {
20496
20998
  cursor: pointer;
@@ -21382,7 +21884,211 @@ function upcomingPage(data) {
21382
21884
  `;
21383
21885
  }
21384
21886
 
21887
+ // src/web/templates/pages/sprint-summary.ts
21888
+ function progressBar(pct) {
21889
+ return `<div class="sprint-progress-bar">
21890
+ <div class="sprint-progress-fill" style="width: ${pct}%"></div>
21891
+ <span class="sprint-progress-label">${pct}%</span>
21892
+ </div>`;
21893
+ }
21894
+ function sprintSummaryPage(data, cached2) {
21895
+ if (!data) {
21896
+ return `
21897
+ <div class="page-header">
21898
+ <h2>Sprint Summary</h2>
21899
+ <div class="subtitle">AI-powered sprint narrative</div>
21900
+ </div>
21901
+ <div class="empty">
21902
+ <h3>No Active Sprint</h3>
21903
+ <p>No active sprint found. Create a sprint and set its status to "active" to see the summary.</p>
21904
+ </div>`;
21905
+ }
21906
+ const statsCards = `
21907
+ <div class="cards">
21908
+ <div class="card">
21909
+ <div class="card-label">Completion</div>
21910
+ <div class="card-value">${data.workItems.completionPct}%</div>
21911
+ <div class="card-sub">${data.workItems.done} / ${data.workItems.total} items done</div>
21912
+ </div>
21913
+ <div class="card">
21914
+ <div class="card-label">Days Remaining</div>
21915
+ <div class="card-value">${data.timeline.daysRemaining}</div>
21916
+ <div class="card-sub">${data.timeline.daysElapsed} of ${data.timeline.totalDays} elapsed</div>
21917
+ </div>
21918
+ <div class="card">
21919
+ <div class="card-label">Epics</div>
21920
+ <div class="card-value">${data.linkedEpics.length}</div>
21921
+ <div class="card-sub">linked to sprint</div>
21922
+ </div>
21923
+ <div class="card">
21924
+ <div class="card-label">Blockers</div>
21925
+ <div class="card-value${data.blockers.length > 0 ? " priority-high" : ""}">${data.blockers.length}</div>
21926
+ <div class="card-sub">${data.openActions.length} open actions</div>
21927
+ </div>
21928
+ </div>`;
21929
+ const epicsTable = data.linkedEpics.length > 0 ? collapsibleSection(
21930
+ "ss-epics",
21931
+ "Linked Epics",
21932
+ `<div class="table-wrap">
21933
+ <table>
21934
+ <thead>
21935
+ <tr><th>ID</th><th>Title</th><th>Status</th><th>Tasks</th></tr>
21936
+ </thead>
21937
+ <tbody>
21938
+ ${data.linkedEpics.map((e) => `
21939
+ <tr>
21940
+ <td><a href="/docs/epic/${escapeHtml(e.id)}">${escapeHtml(e.id)}</a></td>
21941
+ <td>${escapeHtml(e.title)}</td>
21942
+ <td>${statusBadge(e.status)}</td>
21943
+ <td>${e.tasksDone} / ${e.tasksTotal}</td>
21944
+ </tr>`).join("")}
21945
+ </tbody>
21946
+ </table>
21947
+ </div>`,
21948
+ { titleTag: "h3" }
21949
+ ) : "";
21950
+ function renderItemRows(items, depth = 0) {
21951
+ return items.flatMap((w) => {
21952
+ const isChild = depth > 0;
21953
+ const isContribution = w.type === "contribution";
21954
+ const rowClass = isContribution ? ' class="contribution-row"' : isChild ? ' class="child-row"' : "";
21955
+ const indent = depth > 0 ? ` style="padding-left: ${0.75 + depth * 1}rem"` : "";
21956
+ const row = `
21957
+ <tr${rowClass}>
21958
+ <td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
21959
+ <td>${escapeHtml(w.title)}</td>
21960
+ <td>${escapeHtml(typeLabel(w.type))}</td>
21961
+ <td>${statusBadge(w.status)}</td>
21962
+ </tr>`;
21963
+ const childRows = w.children ? renderItemRows(w.children, depth + 1) : [];
21964
+ return [row, ...childRows];
21965
+ });
21966
+ }
21967
+ const workItemRows = renderItemRows(data.workItems.items);
21968
+ const workItemsSection = workItemRows.length > 0 ? collapsibleSection(
21969
+ "ss-work-items",
21970
+ "Work Items",
21971
+ `<div class="table-wrap">
21972
+ <table>
21973
+ <thead>
21974
+ <tr><th>ID</th><th>Title</th><th>Type</th><th>Status</th></tr>
21975
+ </thead>
21976
+ <tbody>
21977
+ ${workItemRows.join("")}
21978
+ </tbody>
21979
+ </table>
21980
+ </div>`,
21981
+ { titleTag: "h3", defaultCollapsed: true }
21982
+ ) : "";
21983
+ const activitySection = data.artifacts.length > 0 ? collapsibleSection(
21984
+ "ss-activity",
21985
+ "Recent Activity",
21986
+ `<div class="table-wrap">
21987
+ <table>
21988
+ <thead>
21989
+ <tr><th>Date</th><th>ID</th><th>Title</th><th>Type</th><th>Action</th></tr>
21990
+ </thead>
21991
+ <tbody>
21992
+ ${data.artifacts.slice(0, 15).map((a) => `
21993
+ <tr>
21994
+ <td>${formatDate(a.date)}</td>
21995
+ <td><a href="/docs/${escapeHtml(a.type)}/${escapeHtml(a.id)}">${escapeHtml(a.id)}</a></td>
21996
+ <td>${escapeHtml(a.title)}</td>
21997
+ <td>${escapeHtml(typeLabel(a.type))}</td>
21998
+ <td>${escapeHtml(a.action)}</td>
21999
+ </tr>`).join("")}
22000
+ </tbody>
22001
+ </table>
22002
+ </div>`,
22003
+ { titleTag: "h3", defaultCollapsed: true }
22004
+ ) : "";
22005
+ const meetingsSection = data.meetings.length > 0 ? collapsibleSection(
22006
+ "ss-meetings",
22007
+ `Meetings (${data.meetings.length})`,
22008
+ `<div class="table-wrap">
22009
+ <table>
22010
+ <thead>
22011
+ <tr><th>Date</th><th>ID</th><th>Title</th></tr>
22012
+ </thead>
22013
+ <tbody>
22014
+ ${data.meetings.map((m) => `
22015
+ <tr>
22016
+ <td>${formatDate(m.date)}</td>
22017
+ <td><a href="/docs/meeting/${escapeHtml(m.id)}">${escapeHtml(m.id)}</a></td>
22018
+ <td>${escapeHtml(m.title)}</td>
22019
+ </tr>`).join("")}
22020
+ </tbody>
22021
+ </table>
22022
+ </div>`,
22023
+ { titleTag: "h3", defaultCollapsed: true }
22024
+ ) : "";
22025
+ const goalHtml = data.sprint.goal ? `<div class="sprint-goal"><strong>Goal:</strong> ${escapeHtml(data.sprint.goal)}</div>` : "";
22026
+ const dateRange = data.sprint.startDate && data.sprint.endDate ? `<span class="text-dim">${formatDate(data.sprint.startDate)} \u2014 ${formatDate(data.sprint.endDate)}</span>` : "";
22027
+ return `
22028
+ <div class="page-header">
22029
+ <h2>${escapeHtml(data.sprint.id)} \u2014 ${escapeHtml(data.sprint.title)} ${statusBadge(data.sprint.status)}</h2>
22030
+ <div class="subtitle">Sprint Summary ${dateRange}</div>
22031
+ </div>
22032
+ ${goalHtml}
22033
+ ${progressBar(data.timeline.percentComplete)}
22034
+ ${statsCards}
22035
+ ${epicsTable}
22036
+ ${workItemsSection}
22037
+ ${activitySection}
22038
+ ${meetingsSection}
22039
+
22040
+ <div class="sprint-ai-section">
22041
+ <h3>AI Summary</h3>
22042
+ ${cached2 ? `<p class="text-dim">Generated ${formatDate(cached2.generatedAt)} at ${cached2.generatedAt.slice(11, 16)} UTC</p>` : `<p class="text-dim">Generate a narrative summary of this sprint's progress, risks, and projections.</p>`}
22043
+ <button class="sprint-generate-btn" onclick="generateSummary()" id="generate-btn">${cached2 ? "Regenerate" : "Generate AI Summary"}</button>
22044
+ <div id="summary-loading" class="sprint-loading" style="display:none">
22045
+ <div class="sprint-spinner"></div>
22046
+ <span>Generating summary...</span>
22047
+ </div>
22048
+ <div id="summary-error" class="sprint-error" style="display:none"></div>
22049
+ <div id="summary-content" class="detail-content"${cached2 ? "" : ' style="display:none"'}>${cached2 ? cached2.html : ""}</div>
22050
+ </div>
22051
+
22052
+ <script>
22053
+ async function generateSummary() {
22054
+ var btn = document.getElementById('generate-btn');
22055
+ var loading = document.getElementById('summary-loading');
22056
+ var errorEl = document.getElementById('summary-error');
22057
+ var content = document.getElementById('summary-content');
22058
+
22059
+ btn.disabled = true;
22060
+ btn.style.display = 'none';
22061
+ loading.style.display = 'flex';
22062
+ errorEl.style.display = 'none';
22063
+ content.style.display = 'none';
22064
+
22065
+ try {
22066
+ var res = await fetch('/api/sprint-summary', {
22067
+ method: 'POST',
22068
+ headers: { 'Content-Type': 'application/json' },
22069
+ body: JSON.stringify({ sprintId: '${escapeHtml(data.sprint.id)}' })
22070
+ });
22071
+ var json = await res.json();
22072
+ if (!res.ok) throw new Error(json.error || 'Failed to generate summary');
22073
+ loading.style.display = 'none';
22074
+ content.innerHTML = json.html;
22075
+ content.style.display = 'block';
22076
+ btn.textContent = 'Regenerate';
22077
+ btn.style.display = '';
22078
+ btn.disabled = false;
22079
+ } catch (e) {
22080
+ loading.style.display = 'none';
22081
+ errorEl.textContent = e.message;
22082
+ errorEl.style.display = 'block';
22083
+ btn.style.display = '';
22084
+ btn.disabled = false;
22085
+ }
22086
+ }
22087
+ </script>`;
22088
+ }
22089
+
21385
22090
  // src/web/router.ts
22091
+ var sprintSummaryCache = /* @__PURE__ */ new Map();
21386
22092
  function handleRequest(req, res, store, projectName, navGroups) {
21387
22093
  const parsed = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
21388
22094
  const pathname = parsed.pathname;
@@ -21428,6 +22134,42 @@ function handleRequest(req, res, store, projectName, navGroups) {
21428
22134
  respond(res, layout({ title: "Upcoming", activePath: "/upcoming", projectName, navGroups }, body));
21429
22135
  return;
21430
22136
  }
22137
+ if (pathname === "/sprint-summary" && req.method === "GET") {
22138
+ const sprintId = parsed.searchParams.get("sprint") ?? void 0;
22139
+ const data = getSprintSummaryData(store, sprintId);
22140
+ const cached2 = data ? sprintSummaryCache.get(data.sprint.id) : void 0;
22141
+ const body = sprintSummaryPage(data, cached2 ? { html: cached2.html, generatedAt: cached2.generatedAt } : void 0);
22142
+ respond(res, layout({ title: "Sprint Summary", activePath: "/sprint-summary", projectName, navGroups }, body));
22143
+ return;
22144
+ }
22145
+ if (pathname === "/api/sprint-summary" && req.method === "POST") {
22146
+ let bodyStr = "";
22147
+ req.on("data", (chunk) => {
22148
+ bodyStr += chunk;
22149
+ });
22150
+ req.on("end", async () => {
22151
+ try {
22152
+ const { sprintId } = JSON.parse(bodyStr || "{}");
22153
+ const data = getSprintSummaryData(store, sprintId);
22154
+ if (!data) {
22155
+ res.writeHead(404, { "Content-Type": "application/json" });
22156
+ res.end(JSON.stringify({ error: "Sprint not found" }));
22157
+ return;
22158
+ }
22159
+ const summary = await generateSprintSummary(data);
22160
+ const html = renderMarkdown(summary);
22161
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
22162
+ sprintSummaryCache.set(data.sprint.id, { html, generatedAt });
22163
+ res.writeHead(200, { "Content-Type": "application/json" });
22164
+ res.end(JSON.stringify({ summary, html, generatedAt }));
22165
+ } catch (err) {
22166
+ console.error("[marvin web] Sprint summary generation error:", err);
22167
+ res.writeHead(500, { "Content-Type": "application/json" });
22168
+ res.end(JSON.stringify({ error: "Failed to generate summary" }));
22169
+ }
22170
+ });
22171
+ return;
22172
+ }
21431
22173
  const boardMatch = pathname.match(/^\/board(?:\/([^/]+))?$/);
21432
22174
  if (boardMatch) {
21433
22175
  const type = boardMatch[1];
@@ -21671,6 +22413,24 @@ function createWebTools(store, projectName, navGroups) {
21671
22413
  };
21672
22414
  },
21673
22415
  { annotations: { readOnlyHint: true } }
22416
+ ),
22417
+ tool22(
22418
+ "get_dashboard_sprint_summary",
22419
+ "Get sprint summary data for the active sprint or a specific sprint. Returns structured data about progress, epics, work items, meetings, and blockers. Works without the web server running.",
22420
+ {
22421
+ sprint: external_exports.string().optional().describe("Sprint ID (e.g. 'SP-001'). Omit for the active sprint.")
22422
+ },
22423
+ async (args) => {
22424
+ const data = getSprintSummaryData(store, args.sprint);
22425
+ if (!data) {
22426
+ const msg = args.sprint ? `Sprint ${args.sprint} not found.` : "No active sprint found.";
22427
+ return { content: [{ type: "text", text: msg }], isError: true };
22428
+ }
22429
+ return {
22430
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
22431
+ };
22432
+ },
22433
+ { annotations: { readOnlyHint: true } }
21674
22434
  )
21675
22435
  ];
21676
22436
  }
@@ -21717,7 +22477,7 @@ async function runSkillAction(action, userPrompt, context) {
21717
22477
  try {
21718
22478
  const mcpServer = createMarvinMcpServer(context.store);
21719
22479
  const allowedTools = action.allowGovernanceTools !== false ? GOVERNANCE_TOOL_NAMES : [];
21720
- const conversation = query({
22480
+ const conversation = query2({
21721
22481
  prompt: userPrompt,
21722
22482
  options: {
21723
22483
  systemPrompt: action.systemPrompt,