mrvn-cli 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -218,9 +218,9 @@ var DocumentStore = class {
218
218
  }
219
219
  }
220
220
  }
221
- list(query6) {
221
+ list(query7) {
222
222
  const results = [];
223
- const types = query6?.type ? [query6.type] : Object.keys(this.typeDirs);
223
+ const types = query7?.type ? [query7.type] : Object.keys(this.typeDirs);
224
224
  for (const type of types) {
225
225
  const dirName = this.typeDirs[type];
226
226
  if (!dirName) continue;
@@ -231,9 +231,9 @@ var DocumentStore = class {
231
231
  const filePath = path3.join(dir, file2);
232
232
  const raw = fs3.readFileSync(filePath, "utf-8");
233
233
  const doc = parseDocument(raw, filePath);
234
- if (query6?.status && doc.frontmatter.status !== query6.status) continue;
235
- if (query6?.owner && doc.frontmatter.owner !== query6.owner) continue;
236
- if (query6?.tag && (!doc.frontmatter.tags || !doc.frontmatter.tags.includes(query6.tag)))
234
+ if (query7?.status && doc.frontmatter.status !== query7.status) continue;
235
+ if (query7?.owner && doc.frontmatter.owner !== query7.owner) continue;
236
+ if (query7?.tag && (!doc.frontmatter.tags || !doc.frontmatter.tags.includes(query7.tag)))
237
237
  continue;
238
238
  results.push(doc);
239
239
  }
@@ -401,7 +401,8 @@ var productOwner = {
401
401
  "Acceptance criteria",
402
402
  "Feature definition and prioritization"
403
403
  ],
404
- documentTypes: ["decision", "question", "action", "feature"]
404
+ documentTypes: ["decision", "question", "action", "feature"],
405
+ contributionTypes: ["stakeholder-feedback", "acceptance-result", "priority-change", "market-insight"]
405
406
  };
406
407
 
407
408
  // src/personas/builtin/delivery-manager.ts
@@ -437,9 +438,11 @@ var deliveryManager = {
437
438
  "Team coordination",
438
439
  "Process governance",
439
440
  "Status tracking",
440
- "Epic scheduling and tracking"
441
+ "Epic scheduling and tracking",
442
+ "Sprint planning and tracking"
441
443
  ],
442
- documentTypes: ["action", "decision", "meeting", "question", "feature", "epic"]
444
+ documentTypes: ["action", "decision", "meeting", "question", "feature", "epic", "sprint"],
445
+ contributionTypes: ["risk-finding", "blocker-report", "dependency-update", "status-assessment"]
443
446
  };
444
447
 
445
448
  // src/personas/builtin/tech-lead.ts
@@ -475,9 +478,11 @@ var techLead = {
475
478
  "Technical decisions",
476
479
  "Implementation guidance",
477
480
  "Non-functional requirements",
478
- "Epic creation and scoping"
481
+ "Epic creation and scoping",
482
+ "Sprint scoping and technical execution"
479
483
  ],
480
- documentTypes: ["decision", "action", "question", "epic"]
484
+ documentTypes: ["decision", "action", "question", "epic", "sprint"],
485
+ contributionTypes: ["action-result", "spike-findings", "technical-assessment", "architecture-review"]
481
486
  };
482
487
 
483
488
  // src/personas/registry.ts
@@ -525,6 +530,7 @@ You have access to governance tools for managing project artifacts:
525
530
  - **Questions** (Q-xxx): List, get, create, and update questions
526
531
  - **Features** (F-xxx): List, get, create, and update feature definitions
527
532
  - **Epics** (E-xxx): List, get, create, and update implementation epics (must link to approved features)
533
+ - **Sprints** (SP-xxx): List, get, create, and update time-boxed iterations with linked epics and delivery dates
528
534
  - **Documents**: Search and read any project document
529
535
  - **Sources**: List source documents and view their processing status and derived artifacts
530
536
 
@@ -15508,6 +15514,60 @@ function createReportTools(store) {
15508
15514
  },
15509
15515
  { annotations: { readOnly: true } }
15510
15516
  ),
15517
+ tool8(
15518
+ "generate_sprint_progress",
15519
+ "Generate progress report for a sprint or all sprints, showing linked epics and tagged work items",
15520
+ {
15521
+ sprint: external_exports.string().optional().describe("Specific sprint ID (e.g. 'SP-001') or omit for all")
15522
+ },
15523
+ async (args) => {
15524
+ const allDocs = store.list();
15525
+ const sprintDocs = store.list({ type: "sprint" });
15526
+ const sprints = sprintDocs.filter((s) => !args.sprint || s.frontmatter.id === args.sprint).map((sprintDoc) => {
15527
+ const sprintId = sprintDoc.frontmatter.id;
15528
+ const linkedEpicIds = sprintDoc.frontmatter.linkedEpics ?? [];
15529
+ const linkedEpics = linkedEpicIds.map((epicId) => {
15530
+ const epic = store.get(epicId);
15531
+ return epic ? { id: epicId, title: epic.frontmatter.title, status: epic.frontmatter.status } : { id: epicId, title: "(not found)", status: "unknown" };
15532
+ });
15533
+ const workItems = allDocs.filter(
15534
+ (d) => d.frontmatter.type !== "sprint" && d.frontmatter.type !== "epic" && d.frontmatter.tags?.includes(`sprint:${sprintId}`)
15535
+ );
15536
+ const byStatus = {};
15537
+ for (const doc of workItems) {
15538
+ byStatus[doc.frontmatter.status] = (byStatus[doc.frontmatter.status] ?? 0) + 1;
15539
+ }
15540
+ const doneCount = (byStatus["done"] ?? 0) + (byStatus["resolved"] ?? 0) + (byStatus["closed"] ?? 0);
15541
+ const total = workItems.length;
15542
+ const completionPct = total > 0 ? Math.round(doneCount / total * 100) : 0;
15543
+ return {
15544
+ id: sprintDoc.frontmatter.id,
15545
+ title: sprintDoc.frontmatter.title,
15546
+ status: sprintDoc.frontmatter.status,
15547
+ goal: sprintDoc.frontmatter.goal,
15548
+ startDate: sprintDoc.frontmatter.startDate,
15549
+ endDate: sprintDoc.frontmatter.endDate,
15550
+ linkedEpics,
15551
+ workItems: {
15552
+ total,
15553
+ done: doneCount,
15554
+ completionPct,
15555
+ byStatus,
15556
+ items: workItems.map((d) => ({
15557
+ id: d.frontmatter.id,
15558
+ title: d.frontmatter.title,
15559
+ type: d.frontmatter.type,
15560
+ status: d.frontmatter.status
15561
+ }))
15562
+ }
15563
+ };
15564
+ });
15565
+ return {
15566
+ content: [{ type: "text", text: JSON.stringify({ sprints }, null, 2) }]
15567
+ };
15568
+ },
15569
+ { annotations: { readOnly: true } }
15570
+ ),
15511
15571
  tool8(
15512
15572
  "generate_feature_progress",
15513
15573
  "Generate progress report for features and their linked epics",
@@ -15554,7 +15614,7 @@ function createReportTools(store) {
15554
15614
  {
15555
15615
  title: external_exports.string().describe("Report title"),
15556
15616
  content: external_exports.string().describe("Full report content in markdown"),
15557
- reportType: external_exports.enum(["status", "risk-register", "gar", "epic-progress", "feature-progress", "custom"]).describe("Type of report"),
15617
+ reportType: external_exports.enum(["status", "risk-register", "gar", "epic-progress", "feature-progress", "sprint-progress", "custom"]).describe("Type of report"),
15558
15618
  tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
15559
15619
  },
15560
15620
  async (args) => {
@@ -15851,19 +15911,316 @@ function createEpicTools(store) {
15851
15911
  ];
15852
15912
  }
15853
15913
 
15914
+ // src/plugins/builtin/tools/contributions.ts
15915
+ import { tool as tool11 } from "@anthropic-ai/claude-agent-sdk";
15916
+ function createContributionTools(store) {
15917
+ return [
15918
+ tool11(
15919
+ "list_contributions",
15920
+ "List all contributions in the project, optionally filtered by persona, contribution type, or status",
15921
+ {
15922
+ persona: external_exports.string().optional().describe("Filter by persona (e.g. 'tech-lead', 'product-owner')"),
15923
+ contributionType: external_exports.string().optional().describe("Filter by contribution type (e.g. 'action-result', 'risk-finding')"),
15924
+ status: external_exports.string().optional().describe("Filter by status")
15925
+ },
15926
+ async (args) => {
15927
+ let docs = store.list({ type: "contribution", status: args.status });
15928
+ if (args.persona) {
15929
+ docs = docs.filter((d) => d.frontmatter.persona === args.persona);
15930
+ }
15931
+ if (args.contributionType) {
15932
+ docs = docs.filter((d) => d.frontmatter.contributionType === args.contributionType);
15933
+ }
15934
+ const summary = docs.map((d) => ({
15935
+ id: d.frontmatter.id,
15936
+ title: d.frontmatter.title,
15937
+ status: d.frontmatter.status,
15938
+ persona: d.frontmatter.persona,
15939
+ contributionType: d.frontmatter.contributionType,
15940
+ aboutArtifact: d.frontmatter.aboutArtifact,
15941
+ created: d.frontmatter.created,
15942
+ tags: d.frontmatter.tags
15943
+ }));
15944
+ return {
15945
+ content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
15946
+ };
15947
+ },
15948
+ { annotations: { readOnly: true } }
15949
+ ),
15950
+ tool11(
15951
+ "get_contribution",
15952
+ "Get the full content of a specific contribution by ID",
15953
+ { id: external_exports.string().describe("Contribution ID (e.g. 'C-001')") },
15954
+ async (args) => {
15955
+ const doc = store.get(args.id);
15956
+ if (!doc) {
15957
+ return {
15958
+ content: [{ type: "text", text: `Contribution ${args.id} not found` }],
15959
+ isError: true
15960
+ };
15961
+ }
15962
+ return {
15963
+ content: [
15964
+ {
15965
+ type: "text",
15966
+ text: JSON.stringify(
15967
+ { ...doc.frontmatter, content: doc.content },
15968
+ null,
15969
+ 2
15970
+ )
15971
+ }
15972
+ ]
15973
+ };
15974
+ },
15975
+ { annotations: { readOnly: true } }
15976
+ ),
15977
+ tool11(
15978
+ "create_contribution",
15979
+ "Create a new contribution record from a persona",
15980
+ {
15981
+ title: external_exports.string().describe("Title of the contribution"),
15982
+ content: external_exports.string().describe("Contribution content \u2014 the input from the persona"),
15983
+ persona: external_exports.string().describe("Persona making the contribution (e.g. 'tech-lead')"),
15984
+ contributionType: external_exports.string().describe("Type of contribution (e.g. 'action-result', 'risk-finding')"),
15985
+ aboutArtifact: external_exports.string().optional().describe("Artifact ID this contribution relates to (e.g. 'A-001')"),
15986
+ status: external_exports.string().optional().describe("Status (default: 'open')"),
15987
+ tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
15988
+ },
15989
+ async (args) => {
15990
+ const frontmatter = {
15991
+ title: args.title,
15992
+ status: args.status ?? "open",
15993
+ persona: args.persona,
15994
+ contributionType: args.contributionType
15995
+ };
15996
+ if (args.aboutArtifact) frontmatter.aboutArtifact = args.aboutArtifact;
15997
+ if (args.tags) frontmatter.tags = args.tags;
15998
+ const doc = store.create("contribution", frontmatter, args.content);
15999
+ return {
16000
+ content: [
16001
+ {
16002
+ type: "text",
16003
+ text: `Created contribution ${doc.frontmatter.id}: ${doc.frontmatter.title}`
16004
+ }
16005
+ ]
16006
+ };
16007
+ }
16008
+ ),
16009
+ tool11(
16010
+ "update_contribution",
16011
+ "Update an existing contribution (e.g. to append an Effects section)",
16012
+ {
16013
+ id: external_exports.string().describe("Contribution ID to update"),
16014
+ title: external_exports.string().optional().describe("New title"),
16015
+ status: external_exports.string().optional().describe("New status"),
16016
+ content: external_exports.string().optional().describe("New content")
16017
+ },
16018
+ async (args) => {
16019
+ const { id, content, ...updates } = args;
16020
+ const doc = store.update(id, updates, content);
16021
+ return {
16022
+ content: [
16023
+ {
16024
+ type: "text",
16025
+ text: `Updated contribution ${doc.frontmatter.id}: ${doc.frontmatter.title}`
16026
+ }
16027
+ ]
16028
+ };
16029
+ }
16030
+ )
16031
+ ];
16032
+ }
16033
+
16034
+ // src/plugins/builtin/tools/sprints.ts
16035
+ import { tool as tool12 } from "@anthropic-ai/claude-agent-sdk";
16036
+ function createSprintTools(store) {
16037
+ return [
16038
+ tool12(
16039
+ "list_sprints",
16040
+ "List all sprints in the project, optionally filtered by status",
16041
+ {
16042
+ status: external_exports.enum(["planned", "active", "completed", "cancelled"]).optional().describe("Filter by sprint status")
16043
+ },
16044
+ async (args) => {
16045
+ const docs = store.list({ type: "sprint", status: args.status });
16046
+ const summary = docs.map((d) => ({
16047
+ id: d.frontmatter.id,
16048
+ title: d.frontmatter.title,
16049
+ status: d.frontmatter.status,
16050
+ goal: d.frontmatter.goal,
16051
+ startDate: d.frontmatter.startDate,
16052
+ endDate: d.frontmatter.endDate,
16053
+ linkedEpics: d.frontmatter.linkedEpics,
16054
+ tags: d.frontmatter.tags
16055
+ }));
16056
+ return {
16057
+ content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
16058
+ };
16059
+ },
16060
+ { annotations: { readOnly: true } }
16061
+ ),
16062
+ tool12(
16063
+ "get_sprint",
16064
+ "Get the full content of a specific sprint by ID",
16065
+ { id: external_exports.string().describe("Sprint ID (e.g. 'SP-001')") },
16066
+ async (args) => {
16067
+ const doc = store.get(args.id);
16068
+ if (!doc) {
16069
+ return {
16070
+ content: [{ type: "text", text: `Sprint ${args.id} not found` }],
16071
+ isError: true
16072
+ };
16073
+ }
16074
+ return {
16075
+ content: [
16076
+ {
16077
+ type: "text",
16078
+ text: JSON.stringify(
16079
+ { ...doc.frontmatter, content: doc.content },
16080
+ null,
16081
+ 2
16082
+ )
16083
+ }
16084
+ ]
16085
+ };
16086
+ },
16087
+ { annotations: { readOnly: true } }
16088
+ ),
16089
+ tool12(
16090
+ "create_sprint",
16091
+ "Create a new sprint with dates, goal, and optionally linked epics",
16092
+ {
16093
+ title: external_exports.string().describe("Sprint title"),
16094
+ content: external_exports.string().describe("Sprint description and objectives"),
16095
+ goal: external_exports.string().describe("Sprint goal \u2014 what this sprint aims to deliver"),
16096
+ startDate: external_exports.string().describe("Sprint start date (ISO format, e.g. '2026-03-01')"),
16097
+ endDate: external_exports.string().describe("Sprint end date (ISO format, e.g. '2026-03-14')"),
16098
+ status: external_exports.enum(["planned", "active", "completed", "cancelled"]).optional().describe("Sprint status (default: 'planned')"),
16099
+ linkedEpics: external_exports.array(external_exports.string()).optional().describe("Epic IDs to link (e.g. ['E-001', 'E-003']). Soft-validated: warns if not found but still creates."),
16100
+ tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
16101
+ },
16102
+ async (args) => {
16103
+ const warnings = [];
16104
+ if (args.linkedEpics) {
16105
+ for (const epicId of args.linkedEpics) {
16106
+ const epic = store.get(epicId);
16107
+ if (!epic) {
16108
+ warnings.push(`Epic ${epicId} not found (linked anyway)`);
16109
+ }
16110
+ }
16111
+ }
16112
+ const frontmatter = {
16113
+ title: args.title,
16114
+ status: args.status ?? "planned",
16115
+ goal: args.goal,
16116
+ startDate: args.startDate,
16117
+ endDate: args.endDate,
16118
+ linkedEpics: args.linkedEpics ?? [],
16119
+ tags: [...args.tags ?? []]
16120
+ };
16121
+ const doc = store.create("sprint", frontmatter, args.content);
16122
+ const sprintId = doc.frontmatter.id;
16123
+ if (args.linkedEpics) {
16124
+ for (const epicId of args.linkedEpics) {
16125
+ const epic = store.get(epicId);
16126
+ if (epic) {
16127
+ const existingTags = epic.frontmatter.tags ?? [];
16128
+ const sprintTag = `sprint:${sprintId}`;
16129
+ if (!existingTags.includes(sprintTag)) {
16130
+ store.update(epicId, { tags: [...existingTags, sprintTag] });
16131
+ }
16132
+ }
16133
+ }
16134
+ }
16135
+ const parts = [`Created sprint ${sprintId}: ${doc.frontmatter.title}`];
16136
+ if (warnings.length > 0) {
16137
+ parts.push(`Warnings: ${warnings.join("; ")}`);
16138
+ }
16139
+ return {
16140
+ content: [{ type: "text", text: parts.join("\n") }]
16141
+ };
16142
+ }
16143
+ ),
16144
+ tool12(
16145
+ "update_sprint",
16146
+ "Update an existing sprint. Cannot change id or type.",
16147
+ {
16148
+ id: external_exports.string().describe("Sprint ID to update"),
16149
+ title: external_exports.string().optional().describe("New title"),
16150
+ status: external_exports.enum(["planned", "active", "completed", "cancelled"]).optional().describe("New status"),
16151
+ content: external_exports.string().optional().describe("New content"),
16152
+ goal: external_exports.string().optional().describe("New sprint goal"),
16153
+ startDate: external_exports.string().optional().describe("New start date"),
16154
+ endDate: external_exports.string().optional().describe("New end date"),
16155
+ linkedEpics: external_exports.array(external_exports.string()).optional().describe("New list of linked epic IDs (replaces existing)"),
16156
+ tags: external_exports.array(external_exports.string()).optional().describe("New tags (replaces existing)")
16157
+ },
16158
+ async (args) => {
16159
+ const { id, content, linkedEpics, ...updates } = args;
16160
+ const existing = store.get(id);
16161
+ if (!existing) {
16162
+ return {
16163
+ content: [{ type: "text", text: `Sprint ${id} not found` }],
16164
+ isError: true
16165
+ };
16166
+ }
16167
+ if (linkedEpics !== void 0) {
16168
+ const oldLinked = existing.frontmatter.linkedEpics ?? [];
16169
+ const sprintTag = `sprint:${id}`;
16170
+ const removed = oldLinked.filter((e) => !linkedEpics.includes(e));
16171
+ for (const epicId of removed) {
16172
+ const epic = store.get(epicId);
16173
+ if (epic) {
16174
+ const tags = epic.frontmatter.tags ?? [];
16175
+ const filtered = tags.filter((t) => t !== sprintTag);
16176
+ if (filtered.length !== tags.length) {
16177
+ store.update(epicId, { tags: filtered });
16178
+ }
16179
+ }
16180
+ }
16181
+ const added = linkedEpics.filter((e) => !oldLinked.includes(e));
16182
+ for (const epicId of added) {
16183
+ const epic = store.get(epicId);
16184
+ if (epic) {
16185
+ const tags = epic.frontmatter.tags ?? [];
16186
+ if (!tags.includes(sprintTag)) {
16187
+ store.update(epicId, { tags: [...tags, sprintTag] });
16188
+ }
16189
+ }
16190
+ }
16191
+ updates.linkedEpics = linkedEpics;
16192
+ }
16193
+ const doc = store.update(id, updates, content);
16194
+ return {
16195
+ content: [
16196
+ {
16197
+ type: "text",
16198
+ text: `Updated sprint ${doc.frontmatter.id}: ${doc.frontmatter.title}`
16199
+ }
16200
+ ]
16201
+ };
16202
+ }
16203
+ )
16204
+ ];
16205
+ }
16206
+
15854
16207
  // src/plugins/common.ts
15855
16208
  var COMMON_REGISTRATIONS = [
15856
16209
  { type: "meeting", dirName: "meetings", idPrefix: "M" },
15857
16210
  { type: "report", dirName: "reports", idPrefix: "R" },
15858
16211
  { type: "feature", dirName: "features", idPrefix: "F" },
15859
- { type: "epic", dirName: "epics", idPrefix: "E" }
16212
+ { type: "epic", dirName: "epics", idPrefix: "E" },
16213
+ { type: "contribution", dirName: "contributions", idPrefix: "C" },
16214
+ { type: "sprint", dirName: "sprints", idPrefix: "SP" }
15860
16215
  ];
15861
16216
  function createCommonTools(store) {
15862
16217
  return [
15863
16218
  ...createMeetingTools(store),
15864
16219
  ...createReportTools(store),
15865
16220
  ...createFeatureTools(store),
15866
- ...createEpicTools(store)
16221
+ ...createEpicTools(store),
16222
+ ...createContributionTools(store),
16223
+ ...createSprintTools(store)
15867
16224
  ];
15868
16225
  }
15869
16226
 
@@ -15873,7 +16230,7 @@ var genericAgilePlugin = {
15873
16230
  name: "Generic Agile",
15874
16231
  description: "Default methodology plugin providing standard agile governance patterns for decisions, actions, and questions.",
15875
16232
  version: "0.1.0",
15876
- documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic"],
16233
+ documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic", "contribution", "sprint"],
15877
16234
  documentTypeRegistrations: [...COMMON_REGISTRATIONS],
15878
16235
  tools: (store) => [...createCommonTools(store)],
15879
16236
  promptFragments: {
@@ -15894,7 +16251,16 @@ var genericAgilePlugin = {
15894
16251
  - Create features as "draft" and approve them when requirements are clear and prioritized.
15895
16252
  - Do NOT create epics \u2014 that is the Tech Lead's responsibility. You can view epics to track progress.
15896
16253
  - Use priority levels (critical, high, medium, low) to communicate business value.
15897
- - Tag features for categorization and cross-referencing.`,
16254
+ - Tag features for categorization and cross-referencing.
16255
+
16256
+ **Contribution Tools:**
16257
+ - **list_contributions** / **get_contribution**: Browse and read contribution records.
16258
+ - **create_contribution**: Record a contribution with persona, type, and optional related artifact.
16259
+ - **update_contribution**: Update a contribution (e.g. append effects).
16260
+ - Available contribution types: stakeholder-feedback, acceptance-result, priority-change, market-insight.
16261
+
16262
+ **Sprint Tools (read-only for awareness):**
16263
+ - **list_sprints** / **get_sprint**: View sprints to understand delivery timelines and iteration scope.`,
15898
16264
  "tech-lead": `You own epics and break approved features into implementation work.
15899
16265
 
15900
16266
  **Epic Tools:**
@@ -15915,7 +16281,19 @@ var genericAgilePlugin = {
15915
16281
  - Only create epics against approved features \u2014 create_epic enforces this.
15916
16282
  - Tag work items (actions, decisions, questions) with \`epic:E-xxx\` to group them under an epic.
15917
16283
  - Collaborate with the Delivery Manager on target dates and effort estimates.
15918
- - Each epic should have a clear scope and definition of done.`,
16284
+ - Each epic should have a clear scope and definition of done.
16285
+
16286
+ **Contribution Tools:**
16287
+ - **list_contributions** / **get_contribution**: Browse and read contribution records.
16288
+ - **create_contribution**: Record a contribution with persona, type, and optional related artifact.
16289
+ - **update_contribution**: Update a contribution (e.g. append effects).
16290
+ - Available contribution types: action-result, spike-findings, technical-assessment, architecture-review.
16291
+
16292
+ **Sprint Tools:**
16293
+ - **list_sprints** / **get_sprint**: View sprints to understand iteration scope and delivery dates.
16294
+ - **update_sprint**: Assign epics to sprints by updating linkedEpics when breaking features into work.
16295
+ - Tag technical actions and decisions with \`sprint:SP-xxx\` to associate them with a sprint.
16296
+ - Use **generate_sprint_progress** to track technical work completion within an iteration.`,
15919
16297
  "delivery-manager": `You track delivery across features and epics, manage schedules, and report on progress.
15920
16298
 
15921
16299
  **Report Tools:**
@@ -15943,27 +16321,53 @@ var genericAgilePlugin = {
15943
16321
  - After generating any report, offer to save it with save_report for audit trail.
15944
16322
  - Proactively flag risks: unowned actions, overdue items, epics linked to deferred features.
15945
16323
  - Use feature progress reports for stakeholder updates and epic progress for sprint-level tracking.
15946
- - Use analyze_meeting after meetings to extract outcomes into governance artifacts.`,
15947
- "*": `You have access to feature, epic, and meeting tools for project coordination:
16324
+ - Use analyze_meeting after meetings to extract outcomes into governance artifacts.
16325
+
16326
+ **Contribution Tools:**
16327
+ - **list_contributions** / **get_contribution**: Browse and read contribution records.
16328
+ - **create_contribution**: Record a contribution with persona, type, and optional related artifact.
16329
+ - **update_contribution**: Update a contribution (e.g. append effects).
16330
+ - Available contribution types: risk-finding, blocker-report, dependency-update, status-assessment.
16331
+
16332
+ **Sprint Tools:**
16333
+ - **list_sprints** / **get_sprint**: Browse and read sprint definitions.
16334
+ - **create_sprint**: Create sprints with dates, goals, and linked epics. Use status "planned" for upcoming sprints or "active"/"completed"/"cancelled" for current/past sprints.
16335
+ - **update_sprint**: Update sprint status, dates, goal, or linked epics. When linkedEpics changes, affected epics are re-tagged automatically.
16336
+ - **generate_sprint_progress**: Progress report for a specific sprint or all sprints \u2014 shows linked epics with statuses, work items tagged \`sprint:SP-xxx\` grouped by status, and done/total completion %.
16337
+ - Use \`save_report\` with reportType "sprint-progress" to persist sprint reports.
16338
+
16339
+ **Sprint Workflow:**
16340
+ - Create sprints with clear goals and date boundaries.
16341
+ - Assign epics to sprints via linkedEpics.
16342
+ - Tag work items (actions, decisions, questions) with \`sprint:SP-xxx\` for sprint scoping.
16343
+ - Track delivery dates and flag at-risk sprints.
16344
+ - Register past/completed sprints for historical tracking.`,
16345
+ "*": `You have access to feature, epic, sprint, and meeting tools for project coordination:
15948
16346
 
15949
16347
  **Features** (F-xxx): Product capabilities defined by the Product Owner. Features progress through draft \u2192 approved \u2192 done.
15950
16348
  **Epics** (E-xxx): Implementation work packages created by the Tech Lead, linked to approved features. Epics progress through planned \u2192 in-progress \u2192 done.
16349
+ **Sprints** (SP-xxx): Time-boxed iterations that group epics and work items with delivery dates. Sprints progress through planned \u2192 active \u2192 completed (or cancelled).
15951
16350
  **Meetings**: Meeting records with attendees, agendas, and notes.
15952
16351
 
15953
- **Key workflow rule:** Epics must link to approved features \u2014 the system enforces this. The Product Owner defines and approves features, the Tech Lead breaks them into epics, and the Delivery Manager tracks dates and progress.
16352
+ **Key workflow rule:** Epics must link to approved features \u2014 the system enforces this. The Product Owner defines and approves features, the Tech Lead breaks them into epics, the Delivery Manager plans sprints and tracks dates and progress. Work items are associated with sprints via \`sprint:SP-xxx\` tags.
15954
16353
 
15955
16354
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15956
16355
  - **create_meeting**: Record meetings with attendees, date, and agenda. The meeting date is required \u2014 extract it from the meeting content or ask the user if not found.
15957
16356
  - **update_meeting**: Update meeting status or notes.
15958
- - **analyze_meeting**: Analyze a meeting to extract decisions, actions, and questions as governance artifacts.`
16357
+ - **analyze_meeting**: Analyze a meeting to extract decisions, actions, and questions as governance artifacts.
16358
+
16359
+ **Contributions** (C-xxx): Structured inputs from personas outside of meetings (e.g. action results, risk findings, stakeholder feedback). Contributions are analyzed to produce governance effects.
16360
+ - **list_contributions** / **get_contribution**: Browse and read contribution records.
16361
+ - **create_contribution**: Record a contribution with persona, type, and optional related artifact.
16362
+ - **update_contribution**: Update a contribution (e.g. append effects).`
15959
16363
  }
15960
16364
  };
15961
16365
 
15962
16366
  // src/plugins/builtin/tools/use-cases.ts
15963
- import { tool as tool11 } from "@anthropic-ai/claude-agent-sdk";
16367
+ import { tool as tool13 } from "@anthropic-ai/claude-agent-sdk";
15964
16368
  function createUseCaseTools(store) {
15965
16369
  return [
15966
- tool11(
16370
+ tool13(
15967
16371
  "list_use_cases",
15968
16372
  "List all extension use cases, optionally filtered by status or extension type",
15969
16373
  {
@@ -15993,7 +16397,7 @@ function createUseCaseTools(store) {
15993
16397
  },
15994
16398
  { annotations: { readOnly: true } }
15995
16399
  ),
15996
- tool11(
16400
+ tool13(
15997
16401
  "get_use_case",
15998
16402
  "Get the full content of a specific use case by ID",
15999
16403
  { id: external_exports.string().describe("Use case ID (e.g. 'UC-001')") },
@@ -16020,7 +16424,7 @@ function createUseCaseTools(store) {
16020
16424
  },
16021
16425
  { annotations: { readOnly: true } }
16022
16426
  ),
16023
- tool11(
16427
+ tool13(
16024
16428
  "create_use_case",
16025
16429
  "Create a new extension use case definition (Phase 1: Assess Extension Use Case)",
16026
16430
  {
@@ -16054,7 +16458,7 @@ function createUseCaseTools(store) {
16054
16458
  };
16055
16459
  }
16056
16460
  ),
16057
- tool11(
16461
+ tool13(
16058
16462
  "update_use_case",
16059
16463
  "Update an existing extension use case",
16060
16464
  {
@@ -16084,10 +16488,10 @@ function createUseCaseTools(store) {
16084
16488
  }
16085
16489
 
16086
16490
  // src/plugins/builtin/tools/tech-assessments.ts
16087
- import { tool as tool12 } from "@anthropic-ai/claude-agent-sdk";
16491
+ import { tool as tool14 } from "@anthropic-ai/claude-agent-sdk";
16088
16492
  function createTechAssessmentTools(store) {
16089
16493
  return [
16090
- tool12(
16494
+ tool14(
16091
16495
  "list_tech_assessments",
16092
16496
  "List all technology assessments, optionally filtered by status",
16093
16497
  {
@@ -16118,7 +16522,7 @@ function createTechAssessmentTools(store) {
16118
16522
  },
16119
16523
  { annotations: { readOnly: true } }
16120
16524
  ),
16121
- tool12(
16525
+ tool14(
16122
16526
  "get_tech_assessment",
16123
16527
  "Get the full content of a specific technology assessment by ID",
16124
16528
  { id: external_exports.string().describe("Tech assessment ID (e.g. 'TA-001')") },
@@ -16145,7 +16549,7 @@ function createTechAssessmentTools(store) {
16145
16549
  },
16146
16550
  { annotations: { readOnly: true } }
16147
16551
  ),
16148
- tool12(
16552
+ tool14(
16149
16553
  "create_tech_assessment",
16150
16554
  "Create a new technology assessment linked to an assessed/approved use case (Phase 2: Assess Extension Technology)",
16151
16555
  {
@@ -16216,7 +16620,7 @@ function createTechAssessmentTools(store) {
16216
16620
  };
16217
16621
  }
16218
16622
  ),
16219
- tool12(
16623
+ tool14(
16220
16624
  "update_tech_assessment",
16221
16625
  "Update an existing technology assessment. The linked use case cannot be changed.",
16222
16626
  {
@@ -16246,10 +16650,10 @@ function createTechAssessmentTools(store) {
16246
16650
  }
16247
16651
 
16248
16652
  // src/plugins/builtin/tools/extension-designs.ts
16249
- import { tool as tool13 } from "@anthropic-ai/claude-agent-sdk";
16653
+ import { tool as tool15 } from "@anthropic-ai/claude-agent-sdk";
16250
16654
  function createExtensionDesignTools(store) {
16251
16655
  return [
16252
- tool13(
16656
+ tool15(
16253
16657
  "list_extension_designs",
16254
16658
  "List all extension designs, optionally filtered by status",
16255
16659
  {
@@ -16279,7 +16683,7 @@ function createExtensionDesignTools(store) {
16279
16683
  },
16280
16684
  { annotations: { readOnly: true } }
16281
16685
  ),
16282
- tool13(
16686
+ tool15(
16283
16687
  "get_extension_design",
16284
16688
  "Get the full content of a specific extension design by ID",
16285
16689
  { id: external_exports.string().describe("Extension design ID (e.g. 'XD-001')") },
@@ -16306,7 +16710,7 @@ function createExtensionDesignTools(store) {
16306
16710
  },
16307
16711
  { annotations: { readOnly: true } }
16308
16712
  ),
16309
- tool13(
16713
+ tool15(
16310
16714
  "create_extension_design",
16311
16715
  "Create a new extension design linked to a recommended tech assessment (Phase 3: Define Extension Target Solution)",
16312
16716
  {
@@ -16374,7 +16778,7 @@ function createExtensionDesignTools(store) {
16374
16778
  };
16375
16779
  }
16376
16780
  ),
16377
- tool13(
16781
+ tool15(
16378
16782
  "update_extension_design",
16379
16783
  "Update an existing extension design. The linked tech assessment cannot be changed.",
16380
16784
  {
@@ -16403,10 +16807,10 @@ function createExtensionDesignTools(store) {
16403
16807
  }
16404
16808
 
16405
16809
  // src/plugins/builtin/tools/aem-reports.ts
16406
- import { tool as tool14 } from "@anthropic-ai/claude-agent-sdk";
16810
+ import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
16407
16811
  function createAemReportTools(store) {
16408
16812
  return [
16409
- tool14(
16813
+ tool16(
16410
16814
  "generate_extension_portfolio",
16411
16815
  "Generate a portfolio view of all use cases with their linked tech assessments and extension designs",
16412
16816
  {},
@@ -16458,7 +16862,7 @@ function createAemReportTools(store) {
16458
16862
  },
16459
16863
  { annotations: { readOnly: true } }
16460
16864
  ),
16461
- tool14(
16865
+ tool16(
16462
16866
  "generate_tech_readiness",
16463
16867
  "Generate a BTP technology readiness report showing service coverage and gaps across assessments",
16464
16868
  {},
@@ -16510,7 +16914,7 @@ function createAemReportTools(store) {
16510
16914
  },
16511
16915
  { annotations: { readOnly: true } }
16512
16916
  ),
16513
- tool14(
16917
+ tool16(
16514
16918
  "generate_phase_status",
16515
16919
  "Generate a phase progress report showing artifact counts and readiness per AEM phase",
16516
16920
  {},
@@ -16572,11 +16976,11 @@ function createAemReportTools(store) {
16572
16976
  import * as fs6 from "fs";
16573
16977
  import * as path6 from "path";
16574
16978
  import * as YAML4 from "yaml";
16575
- import { tool as tool15 } from "@anthropic-ai/claude-agent-sdk";
16979
+ import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
16576
16980
  var PHASES = ["assess-use-case", "assess-technology", "define-solution"];
16577
16981
  function createAemPhaseTools(store, marvinDir) {
16578
16982
  return [
16579
- tool15(
16983
+ tool17(
16580
16984
  "get_current_phase",
16581
16985
  "Get the current AEM phase from project configuration",
16582
16986
  {},
@@ -16597,7 +17001,7 @@ function createAemPhaseTools(store, marvinDir) {
16597
17001
  },
16598
17002
  { annotations: { readOnly: true } }
16599
17003
  ),
16600
- tool15(
17004
+ tool17(
16601
17005
  "advance_phase",
16602
17006
  "Advance to the next AEM phase. Performs soft gate checks and warns if artifacts are incomplete, but does not block.",
16603
17007
  {
@@ -17372,7 +17776,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17372
17776
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17373
17777
 
17374
17778
  // src/skills/action-tools.ts
17375
- import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
17779
+ import { tool as tool18 } from "@anthropic-ai/claude-agent-sdk";
17376
17780
 
17377
17781
  // src/skills/action-runner.ts
17378
17782
  import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
@@ -17438,7 +17842,7 @@ function createSkillActionTools(skills, context) {
17438
17842
  if (!skill.actions) continue;
17439
17843
  for (const action of skill.actions) {
17440
17844
  tools.push(
17441
- tool16(
17845
+ tool18(
17442
17846
  `${skill.id}__${action.id}`,
17443
17847
  action.description,
17444
17848
  {
@@ -17530,10 +17934,10 @@ ${lines.join("\n\n")}`;
17530
17934
  }
17531
17935
 
17532
17936
  // src/mcp/persona-tools.ts
17533
- import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
17937
+ import { tool as tool19 } from "@anthropic-ai/claude-agent-sdk";
17534
17938
  function createPersonaTools(ctx, marvinDir) {
17535
17939
  return [
17536
- tool17(
17940
+ tool19(
17537
17941
  "set_persona",
17538
17942
  "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.",
17539
17943
  {
@@ -17563,7 +17967,7 @@ ${summaries}`
17563
17967
  };
17564
17968
  }
17565
17969
  ),
17566
- tool17(
17970
+ tool19(
17567
17971
  "get_persona_guidance",
17568
17972
  "Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
17569
17973
  {
@@ -19190,7 +19594,8 @@ function getDirNameForType(store, type) {
19190
19594
  meeting: "meetings",
19191
19595
  report: "reports",
19192
19596
  feature: "features",
19193
- epic: "epics"
19597
+ epic: "epics",
19598
+ sprint: "sprints"
19194
19599
  };
19195
19600
  return typeDir[type] ?? `${type}s`;
19196
19601
  }
@@ -19687,12 +20092,417 @@ Analyzing meeting: ${meetingDoc.frontmatter.title}`));
19687
20092
  });
19688
20093
  }
19689
20094
 
20095
+ // src/cli/commands/contribute.ts
20096
+ import chalk15 from "chalk";
20097
+
20098
+ // src/contributions/contribute.ts
20099
+ import chalk14 from "chalk";
20100
+ import ora5 from "ora";
20101
+ import { query as query6 } from "@anthropic-ai/claude-agent-sdk";
20102
+
20103
+ // src/contributions/prompts.ts
20104
+ function buildContributeSystemPrompt(persona, contributionType, projectConfig, isDraft) {
20105
+ const parts = [];
20106
+ parts.push(persona.systemPrompt);
20107
+ parts.push(`
20108
+ ## Project Context
20109
+ - **Project Name:** ${projectConfig.name}
20110
+ - **Methodology:** ${projectConfig.methodology ?? "Generic Agile"}
20111
+ `);
20112
+ parts.push(`
20113
+ ## Contribution Analysis Task
20114
+ You are processing a **${contributionType}** contribution from the **${persona.name}**.
20115
+ Review the contribution content and determine what governance effects should follow.
20116
+
20117
+ Possible effects include:
20118
+ 1. **Create** new artifacts \u2014 decisions (D-xxx), actions (A-xxx), questions (Q-xxx)
20119
+ 2. **Update** existing artifacts \u2014 change status, add information, close items
20120
+ `);
20121
+ parts.push(buildContributionTypeInstructions(contributionType));
20122
+ if (isDraft) {
20123
+ parts.push(`
20124
+ ## Mode: Draft Proposal
20125
+ Present your findings as a structured proposal. Do NOT create or update any artifacts.
20126
+ Format your response as:
20127
+
20128
+ ### Proposed Effects
20129
+ For each effect:
20130
+ - **Action:** [create/update]
20131
+ - **Target:** [new artifact type or existing artifact ID]
20132
+ - **Details:** [what would be created or changed]
20133
+ - **Rationale:** [why this effect follows from the contribution]
20134
+
20135
+ ### Summary
20136
+ Provide a brief summary of the contribution analysis and recommendations.
20137
+ `);
20138
+ } else {
20139
+ parts.push(`
20140
+ ## Mode: Direct Execution
20141
+ Use the MCP tools to create and update artifacts directly:
20142
+ - Use \`create_decision\` / \`create_action\` / \`create_question\` for new artifacts
20143
+ - Use \`update_decision\` / \`update_action\` / \`update_question\` to modify existing ones
20144
+ - Use \`list_decisions\` / \`list_actions\` / \`list_questions\` to check existing artifacts before creating or updating
20145
+
20146
+ Before creating artifacts, check existing ones using the list tools to avoid duplicates.
20147
+
20148
+ For EVERY artifact you create or update, include:
20149
+ - A \`source:<contribution-id>\` tag in the tags array (the contribution ID is provided in the user prompt)
20150
+ - Clear title and detailed content with context from the contribution
20151
+
20152
+ After processing all effects, provide a summary of what was created and updated.
20153
+ `);
20154
+ }
20155
+ return parts.join("\n");
20156
+ }
20157
+ function buildContributionTypeInstructions(contributionType) {
20158
+ const instructions = {
20159
+ "action-result": `
20160
+ ### Type-Specific Guidance: Action Result
20161
+ The contributor is reporting results of a completed action.
20162
+ - Update the referenced action's status to "done" if results are conclusive
20163
+ - Check if the results answer any open questions \u2014 if so, update those questions
20164
+ - Check if results challenge any existing decisions \u2014 if so, create a new question or update the decision
20165
+ - If results reveal new work, create new actions`,
20166
+ "spike-findings": `
20167
+ ### Type-Specific Guidance: Spike Findings
20168
+ The contributor is sharing findings from a technical investigation (spike).
20169
+ - Create decisions for any architectural or technical choices made
20170
+ - Update or close the original spike action if referenced
20171
+ - Create new actions for follow-up implementation work
20172
+ - Create questions for areas needing further investigation`,
20173
+ "technical-assessment": `
20174
+ ### Type-Specific Guidance: Technical Assessment
20175
+ The contributor is providing a technical evaluation.
20176
+ - Create decisions for technical recommendations
20177
+ - Create actions for remediation or improvement tasks
20178
+ - Create questions for areas needing more investigation
20179
+ - Flag any risks by tagging artifacts with "risk"`,
20180
+ "architecture-review": `
20181
+ ### Type-Specific Guidance: Architecture Review
20182
+ The contributor is documenting an architecture review.
20183
+ - Create decisions for architectural choices and trade-offs
20184
+ - Create actions for any architectural changes needed
20185
+ - Create questions for design concerns or alternatives to evaluate
20186
+ - Update existing decisions if the review validates or challenges them`,
20187
+ "stakeholder-feedback": `
20188
+ ### Type-Specific Guidance: Stakeholder Feedback
20189
+ The contributor is relaying feedback from stakeholders.
20190
+ - Create or update decisions based on stakeholder direction
20191
+ - Create actions for requested changes or follow-ups
20192
+ - Create questions for items needing clarification
20193
+ - Update priorities on existing artifacts if feedback changes priorities`,
20194
+ "acceptance-result": `
20195
+ ### Type-Specific Guidance: Acceptance Result
20196
+ The contributor is reporting results of acceptance testing.
20197
+ - Update the referenced action/feature status based on pass/fail
20198
+ - Create new actions for any rework needed
20199
+ - Create questions for ambiguous acceptance criteria
20200
+ - If accepted, mark related actions as "done"`,
20201
+ "priority-change": `
20202
+ ### Type-Specific Guidance: Priority Change
20203
+ The contributor is communicating a change in priorities.
20204
+ - Update priority fields on affected artifacts
20205
+ - Create decisions documenting the priority change rationale
20206
+ - Create actions for any re-planning needed
20207
+ - Flag any blocked or impacted items`,
20208
+ "market-insight": `
20209
+ ### Type-Specific Guidance: Market Insight
20210
+ The contributor is sharing market intelligence or competitive information.
20211
+ - Create decisions if the insight drives product direction changes
20212
+ - Create questions for areas needing further market research
20213
+ - Create actions for competitive response tasks
20214
+ - Update existing features or priorities if affected`,
20215
+ "risk-finding": `
20216
+ ### Type-Specific Guidance: Risk Finding
20217
+ The contributor is identifying a project risk.
20218
+ - Create actions for risk mitigation tasks
20219
+ - Create decisions for risk response strategies
20220
+ - Create questions for risks needing further assessment
20221
+ - Tag all related artifacts with "risk" for tracking`,
20222
+ "blocker-report": `
20223
+ ### Type-Specific Guidance: Blocker Report
20224
+ The contributor is reporting a blocker.
20225
+ - Create actions to resolve the blocker
20226
+ - Update blocked artifacts' status or add blocking context
20227
+ - Create questions for blockers needing external input
20228
+ - Escalate by creating high-priority actions if critical`,
20229
+ "dependency-update": `
20230
+ ### Type-Specific Guidance: Dependency Update
20231
+ The contributor is reporting on external or internal dependencies.
20232
+ - Update affected actions with dependency information
20233
+ - Create new actions for dependency-related tasks
20234
+ - Create questions for uncertain dependencies
20235
+ - Update timelines or flag schedule risks`,
20236
+ "status-assessment": `
20237
+ ### Type-Specific Guidance: Status Assessment
20238
+ The contributor is providing a delivery status assessment.
20239
+ - Update action statuses based on assessment
20240
+ - Create actions for corrective measures
20241
+ - Create questions for items needing investigation
20242
+ - Create decisions for any process changes needed`
20243
+ };
20244
+ return instructions[contributionType] ?? `
20245
+ ### Type-Specific Guidance
20246
+ Analyze the contribution and determine appropriate governance effects based on the content.`;
20247
+ }
20248
+ function buildContributeUserPrompt(contributionId, contributionType, content, aboutArtifact, isDraft) {
20249
+ const mode = isDraft ? "propose" : "execute";
20250
+ const aboutLine = aboutArtifact ? `
20251
+ **Related Artifact:** ${aboutArtifact}` : "";
20252
+ return `Please analyze the following contribution and ${mode} governance effects.
20253
+
20254
+ **Contribution ID:** ${contributionId}
20255
+ **Contribution Type:** ${contributionType}${aboutLine}
20256
+
20257
+ ---
20258
+ ${content}
20259
+ ---
20260
+
20261
+ Analyze this contribution thoroughly and ${mode} all relevant governance effects (create new artifacts, update existing ones). Tag each created or updated artifact with "source:${contributionId}" for traceability.`;
20262
+ }
20263
+
20264
+ // src/contributions/contribute.ts
20265
+ async function contributeFromPersona(options) {
20266
+ const { marvinDir, prompt, aboutArtifact, draft } = options;
20267
+ const config2 = getConfig(marvinDir);
20268
+ const personaId = resolvePersonaId(options.persona);
20269
+ const persona = getPersona(personaId);
20270
+ const allowedTypes = persona.contributionTypes ?? [];
20271
+ if (allowedTypes.length > 0 && !allowedTypes.includes(options.contributionType)) {
20272
+ throw new Error(
20273
+ `Contribution type "${options.contributionType}" is not valid for persona "${persona.name}". Allowed types: ${allowedTypes.join(", ")}`
20274
+ );
20275
+ }
20276
+ const plugin = resolvePlugin(config2.project.methodology);
20277
+ const registrations = plugin?.documentTypeRegistrations ?? [];
20278
+ const store = new DocumentStore(marvinDir, registrations);
20279
+ const contributionFrontmatter = {
20280
+ title: `${options.contributionType}: ${prompt.slice(0, 60)}${prompt.length > 60 ? "..." : ""}`,
20281
+ status: "open",
20282
+ persona: personaId,
20283
+ contributionType: options.contributionType
20284
+ };
20285
+ if (aboutArtifact) contributionFrontmatter.aboutArtifact = aboutArtifact;
20286
+ const contributionDoc = store.create("contribution", contributionFrontmatter, prompt);
20287
+ const contributionId = contributionDoc.frontmatter.id;
20288
+ const createdArtifacts = [];
20289
+ const updatedArtifacts = [];
20290
+ if (!draft) {
20291
+ const originalCreate = store.create.bind(store);
20292
+ store.create = (type, frontmatter, content) => {
20293
+ const tags = frontmatter.tags ?? [];
20294
+ const sourceTag = `source:${contributionId}`;
20295
+ if (!tags.includes(sourceTag)) {
20296
+ tags.push(sourceTag);
20297
+ }
20298
+ const doc = originalCreate(type, { ...frontmatter, source: contributionId, tags }, content);
20299
+ createdArtifacts.push(doc.frontmatter.id);
20300
+ return doc;
20301
+ };
20302
+ const originalUpdate = store.update.bind(store);
20303
+ store.update = (id, updates, content) => {
20304
+ if (id === contributionId) {
20305
+ return originalUpdate(id, updates, content);
20306
+ }
20307
+ const existing = store.get(id);
20308
+ const existingTags = existing?.frontmatter.tags ?? [];
20309
+ const sourceTag = `source:${contributionId}`;
20310
+ if (!existingTags.includes(sourceTag)) {
20311
+ existingTags.push(sourceTag);
20312
+ }
20313
+ const doc = originalUpdate(id, { ...updates, tags: existingTags }, content);
20314
+ if (!updatedArtifacts.includes(id)) {
20315
+ updatedArtifacts.push(id);
20316
+ }
20317
+ return doc;
20318
+ };
20319
+ }
20320
+ const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
20321
+ const mcpServer = createMarvinMcpServer(store, { pluginTools });
20322
+ const systemPrompt = buildContributeSystemPrompt(persona, options.contributionType, config2.project, draft);
20323
+ const userPrompt = buildContributeUserPrompt(
20324
+ contributionId,
20325
+ options.contributionType,
20326
+ prompt,
20327
+ aboutArtifact,
20328
+ draft
20329
+ );
20330
+ const spinner = ora5({ text: `Processing contribution ${contributionId}...`, color: "cyan" });
20331
+ spinner.start();
20332
+ try {
20333
+ const allowedTools = draft ? [
20334
+ "mcp__marvin-governance__list_decisions",
20335
+ "mcp__marvin-governance__list_actions",
20336
+ "mcp__marvin-governance__list_questions",
20337
+ "mcp__marvin-governance__get_decision",
20338
+ "mcp__marvin-governance__get_action",
20339
+ "mcp__marvin-governance__get_question"
20340
+ ] : [
20341
+ "mcp__marvin-governance__create_decision",
20342
+ "mcp__marvin-governance__create_action",
20343
+ "mcp__marvin-governance__create_question",
20344
+ "mcp__marvin-governance__update_decision",
20345
+ "mcp__marvin-governance__update_action",
20346
+ "mcp__marvin-governance__update_question",
20347
+ "mcp__marvin-governance__list_decisions",
20348
+ "mcp__marvin-governance__list_actions",
20349
+ "mcp__marvin-governance__list_questions",
20350
+ "mcp__marvin-governance__get_decision",
20351
+ "mcp__marvin-governance__get_action",
20352
+ "mcp__marvin-governance__get_question"
20353
+ ];
20354
+ const conversation = query6({
20355
+ prompt: userPrompt,
20356
+ options: {
20357
+ systemPrompt,
20358
+ mcpServers: { "marvin-governance": mcpServer },
20359
+ permissionMode: "acceptEdits",
20360
+ maxTurns: 10,
20361
+ tools: [],
20362
+ allowedTools
20363
+ }
20364
+ });
20365
+ for await (const message of conversation) {
20366
+ handleContributeMessage(message, spinner);
20367
+ }
20368
+ const effects = [...createdArtifacts, ...updatedArtifacts];
20369
+ if (!draft && effects.length > 0) {
20370
+ appendEffectsToContribution(store, contributionId, contributionDoc.content, createdArtifacts, updatedArtifacts, store);
20371
+ }
20372
+ spinner.stop();
20373
+ if (draft) {
20374
+ console.log(chalk14.dim(`
20375
+ Draft proposal complete. No artifacts were created or updated.`));
20376
+ console.log(chalk14.dim(`Use --no-draft to execute effects directly.`));
20377
+ } else {
20378
+ const totalEffects = createdArtifacts.length + updatedArtifacts.length;
20379
+ console.log(chalk14.green(`
20380
+ Contribution ${contributionId} processed: ${totalEffects} effect${totalEffects === 1 ? "" : "s"}`));
20381
+ if (createdArtifacts.length > 0) {
20382
+ console.log(chalk14.dim(` Created: ${createdArtifacts.join(", ")}`));
20383
+ }
20384
+ if (updatedArtifacts.length > 0) {
20385
+ console.log(chalk14.dim(` Updated: ${updatedArtifacts.join(", ")}`));
20386
+ }
20387
+ }
20388
+ return { contributionId, effects, draft };
20389
+ } catch (err) {
20390
+ spinner.stop();
20391
+ throw err;
20392
+ }
20393
+ }
20394
+ function appendEffectsToContribution(store, contributionId, existingContent, created, updated, storeInstance) {
20395
+ const lines = [];
20396
+ if (created.length > 0) {
20397
+ lines.push("### Created");
20398
+ for (const id of created) {
20399
+ const doc = storeInstance.get(id);
20400
+ const title = doc ? doc.frontmatter.title : id;
20401
+ lines.push(`- ${id}: ${title}`);
20402
+ }
20403
+ }
20404
+ if (updated.length > 0) {
20405
+ lines.push("### Updated");
20406
+ for (const id of updated) {
20407
+ const doc = storeInstance.get(id);
20408
+ const title = doc ? doc.frontmatter.title : id;
20409
+ lines.push(`- ${id}: ${title}`);
20410
+ }
20411
+ }
20412
+ const effectsSection = `
20413
+
20414
+ ## Effects
20415
+ ${lines.join("\n")}`;
20416
+ const updatedContent = existingContent + effectsSection;
20417
+ store.update(contributionId, { status: "processed" }, updatedContent);
20418
+ }
20419
+ function handleContributeMessage(message, spinner) {
20420
+ switch (message.type) {
20421
+ case "assistant": {
20422
+ spinner.stop();
20423
+ const textBlocks = message.message.content.filter(
20424
+ (b) => b.type === "text"
20425
+ );
20426
+ if (textBlocks.length > 0) {
20427
+ console.log(
20428
+ chalk14.cyan("\nMarvin: ") + textBlocks.map((b) => b.text).join("\n")
20429
+ );
20430
+ }
20431
+ break;
20432
+ }
20433
+ case "system": {
20434
+ if (message.subtype === "init") {
20435
+ spinner.start("Analyzing contribution...");
20436
+ }
20437
+ break;
20438
+ }
20439
+ case "result": {
20440
+ spinner.stop();
20441
+ if (message.subtype !== "success") {
20442
+ console.log(
20443
+ chalk14.red(`
20444
+ Contribution analysis ended with error: ${message.subtype}`)
20445
+ );
20446
+ }
20447
+ break;
20448
+ }
20449
+ }
20450
+ }
20451
+
20452
+ // src/cli/commands/contribute.ts
20453
+ async function contributeCommand(options) {
20454
+ const project = loadProject();
20455
+ const marvinDir = project.marvinDir;
20456
+ if (!options.as) {
20457
+ console.log(chalk15.red("Missing required option: --as <persona>"));
20458
+ console.log(chalk15.dim('Example: marvin contribute --as tl --type action-result --prompt "..."'));
20459
+ return;
20460
+ }
20461
+ if (!options.type) {
20462
+ console.log(chalk15.red("Missing required option: --type <contribution-type>"));
20463
+ const personaId2 = resolvePersonaId(options.as);
20464
+ const persona2 = getPersona(personaId2);
20465
+ if (persona2?.contributionTypes?.length) {
20466
+ console.log(chalk15.dim(`Available types for ${persona2.name}: ${persona2.contributionTypes.join(", ")}`));
20467
+ }
20468
+ return;
20469
+ }
20470
+ if (!options.prompt) {
20471
+ console.log(chalk15.red("Missing required option: --prompt <text>"));
20472
+ console.log(chalk15.dim("Provide the contribution content via --prompt."));
20473
+ return;
20474
+ }
20475
+ const personaId = resolvePersonaId(options.as);
20476
+ const persona = getPersona(personaId);
20477
+ if (!persona) {
20478
+ console.log(chalk15.red(`Unknown persona: ${options.as}`));
20479
+ return;
20480
+ }
20481
+ const isDraft = options.draft !== false;
20482
+ console.log(chalk15.bold(`
20483
+ Contribution: ${options.type}`));
20484
+ console.log(chalk15.dim(`Persona: ${persona.name}`));
20485
+ console.log(chalk15.dim(`Mode: ${isDraft ? "draft (propose only)" : "direct (execute effects)"}`));
20486
+ if (options.about) {
20487
+ console.log(chalk15.dim(`About: ${options.about}`));
20488
+ }
20489
+ console.log();
20490
+ await contributeFromPersona({
20491
+ marvinDir,
20492
+ persona: options.as,
20493
+ contributionType: options.type,
20494
+ prompt: options.prompt,
20495
+ aboutArtifact: options.about,
20496
+ draft: isDraft
20497
+ });
20498
+ }
20499
+
19690
20500
  // src/cli/program.ts
19691
20501
  function createProgram() {
19692
20502
  const program = new Command();
19693
20503
  program.name("marvin").description(
19694
20504
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
19695
- ).version("0.2.3");
20505
+ ).version("0.2.5");
19696
20506
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
19697
20507
  await initCommand();
19698
20508
  });
@@ -19735,6 +20545,9 @@ function createProgram() {
19735
20545
  program.command("analyze <meeting-id>").description("Analyze a meeting to extract decisions, actions, and questions").option("--draft", "Propose artifacts without creating them (default)").option("--no-draft", "Create artifacts directly via MCP tools").option("--as <persona>", "Persona for analysis (default: delivery-manager)").action(async (meetingId, options) => {
19736
20546
  await analyzeCommand(meetingId, options);
19737
20547
  });
20548
+ program.command("contribute").description("Submit a structured contribution from a persona to generate governance effects").requiredOption("--as <persona>", "Persona making the contribution (po, dm, tl)").requiredOption("--type <type>", "Contribution type (e.g. action-result, risk-finding)").requiredOption("--prompt <text>", "Contribution content").option("--about <artifact-id>", "Related artifact ID (e.g. A-001)").option("--draft", "Propose effects without executing (default)").option("--no-draft", "Execute effects directly").action(async (options) => {
20549
+ await contributeCommand(options);
20550
+ });
19738
20551
  program.command("import <path>").description("Import documents or sources from external paths").option("--dry-run", "Preview without writing files").option(
19739
20552
  "--conflict <strategy>",
19740
20553
  "ID conflict strategy: renumber, skip, overwrite (default: renumber)"