mrvn-cli 0.2.0 → 0.2.2

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
@@ -275,7 +275,7 @@ var DocumentStore = class {
275
275
  updated: now,
276
276
  ...cleaned
277
277
  };
278
- const fileName = type === "meeting" ? `${now.slice(0, 10)}-${slugify(fullFrontmatter.title)}.md` : `${id}.md`;
278
+ const fileName = type === "meeting" ? `${cleaned.date?.slice(0, 10) ?? now.slice(0, 10)}-${slugify(fullFrontmatter.title)}.md` : `${id}.md`;
279
279
  const filePath = path3.join(dir, fileName);
280
280
  const doc = {
281
281
  frontmatter: fullFrontmatter,
@@ -299,7 +299,7 @@ var DocumentStore = class {
299
299
  }
300
300
  const dir = path3.join(this.docsDir, dirName);
301
301
  fs3.mkdirSync(dir, { recursive: true });
302
- const fileName = type === "meeting" ? `${frontmatter.created.slice(0, 10)}-${slugify(frontmatter.title)}.md` : `${frontmatter.id}.md`;
302
+ const fileName = type === "meeting" ? `${frontmatter.date?.slice(0, 10) ?? frontmatter.created.slice(0, 10)}-${slugify(frontmatter.title)}.md` : `${frontmatter.id}.md`;
303
303
  const filePath = path3.join(dir, fileName);
304
304
  const doc = { frontmatter, content, filePath };
305
305
  fs3.writeFileSync(filePath, serializeDocument(doc), "utf-8");
@@ -15176,7 +15176,7 @@ function createMeetingTools(store) {
15176
15176
  owner: external_exports.string().optional().describe("Meeting organizer"),
15177
15177
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
15178
15178
  attendees: external_exports.array(external_exports.string()).optional().describe("List of attendees"),
15179
- date: external_exports.string().optional().describe("Meeting date (ISO format)")
15179
+ date: external_exports.string().describe("Date the meeting took place (ISO format, e.g. '2025-01-15'). Extract from the meeting content. If not found, ask the user before calling this tool.")
15180
15180
  },
15181
15181
  async (args) => {
15182
15182
  const frontmatter = {
@@ -15186,7 +15186,7 @@ function createMeetingTools(store) {
15186
15186
  if (args.owner) frontmatter.owner = args.owner;
15187
15187
  if (args.tags) frontmatter.tags = args.tags;
15188
15188
  if (args.attendees) frontmatter.attendees = args.attendees;
15189
- if (args.date) frontmatter.date = args.date;
15189
+ frontmatter.date = args.date;
15190
15190
  const doc = store.create(
15191
15191
  "meeting",
15192
15192
  frontmatter,
@@ -15886,7 +15886,7 @@ var genericAgilePlugin = {
15886
15886
 
15887
15887
  **Meeting Tools:**
15888
15888
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15889
- - **create_meeting**: Record new meetings with attendees, date, and agenda.
15889
+ - **create_meeting**: Record new 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.
15890
15890
  - **update_meeting**: Update meeting status or notes after completion.
15891
15891
  - **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
15892
15892
 
@@ -15907,7 +15907,7 @@ var genericAgilePlugin = {
15907
15907
 
15908
15908
  **Meeting Tools:**
15909
15909
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15910
- - **create_meeting**: Record new meetings with attendees, date, and agenda.
15910
+ - **create_meeting**: Record new 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.
15911
15911
  - **update_meeting**: Update meeting status or notes after completion.
15912
15912
  - **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
15913
15913
 
@@ -15935,7 +15935,7 @@ var genericAgilePlugin = {
15935
15935
 
15936
15936
  **Meeting Tools:**
15937
15937
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15938
- - **create_meeting**: Record new meetings with attendees, date, and agenda.
15938
+ - **create_meeting**: Record new 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.
15939
15939
  - **update_meeting**: Update meeting status or notes after completion.
15940
15940
  - **analyze_meeting**: Analyze a completed meeting to extract decisions, actions, and questions. Use this to ensure meeting outcomes are properly tracked as governance artifacts.
15941
15941
 
@@ -15953,7 +15953,7 @@ var genericAgilePlugin = {
15953
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.
15954
15954
 
15955
15955
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15956
- - **create_meeting**: Record meetings with attendees, date, and agenda.
15956
+ - **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
15957
  - **update_meeting**: Update meeting status or notes.
15958
15958
  - **analyze_meeting**: Analyze a meeting to extract decisions, actions, and questions as governance artifacts.`
15959
15959
  }
@@ -17452,6 +17452,213 @@ function createSkillActionTools(skills, context) {
17452
17452
  return tools;
17453
17453
  }
17454
17454
 
17455
+ // src/mcp/persona-context.ts
17456
+ var PersonaContextManager = class {
17457
+ activePersona = null;
17458
+ setPersona(idOrShortName) {
17459
+ const persona = getPersona(idOrShortName);
17460
+ if (persona) {
17461
+ this.activePersona = persona;
17462
+ }
17463
+ return persona;
17464
+ }
17465
+ getActivePersona() {
17466
+ return this.activePersona;
17467
+ }
17468
+ clearPersona() {
17469
+ this.activePersona = null;
17470
+ }
17471
+ isDocumentTypeAllowed(docType) {
17472
+ if (!this.activePersona) return true;
17473
+ return this.activePersona.documentTypes.includes(docType);
17474
+ }
17475
+ };
17476
+ function buildMcpGuidance(persona, marvinDir) {
17477
+ const parts = [];
17478
+ parts.push(`# Active Persona: ${persona.name} (${persona.shortName})`);
17479
+ parts.push(`
17480
+ ${persona.description}`);
17481
+ parts.push(`
17482
+ ## Focus Areas`);
17483
+ for (const area of persona.focusAreas) {
17484
+ parts.push(`- ${area}`);
17485
+ }
17486
+ parts.push(`
17487
+ ## Allowed Document Types`);
17488
+ parts.push(persona.documentTypes.join(", "));
17489
+ parts.push(`
17490
+ ## Behavioral Instructions`);
17491
+ parts.push(persona.systemPrompt);
17492
+ try {
17493
+ const config2 = loadProjectConfig(marvinDir);
17494
+ const plugin = resolvePlugin(config2.methodology);
17495
+ if (plugin) {
17496
+ const fragment = getPluginPromptFragment(plugin, persona.id);
17497
+ if (fragment) {
17498
+ parts.push(`
17499
+ ## Plugin Rules`);
17500
+ parts.push(fragment);
17501
+ }
17502
+ }
17503
+ const allSkills = loadAllSkills(marvinDir);
17504
+ const skillIds = resolveSkillsForPersona(
17505
+ persona.id,
17506
+ config2.skills,
17507
+ allSkills
17508
+ );
17509
+ if (skillIds.length > 0) {
17510
+ const fragment = getSkillPromptFragment(skillIds, allSkills, persona.id);
17511
+ if (fragment) {
17512
+ parts.push(`
17513
+ ## Skill Rules`);
17514
+ parts.push(fragment);
17515
+ }
17516
+ }
17517
+ } catch {
17518
+ }
17519
+ return parts.join("\n");
17520
+ }
17521
+ function buildPersonaSummaries() {
17522
+ const personas = listPersonas();
17523
+ const lines = personas.map(
17524
+ (p) => `- **${p.name}** (${p.shortName}): ${p.description}
17525
+ Document types: ${p.documentTypes.join(", ")}`
17526
+ );
17527
+ return `# Available Personas
17528
+
17529
+ ${lines.join("\n\n")}`;
17530
+ }
17531
+
17532
+ // src/mcp/persona-tools.ts
17533
+ import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
17534
+ function createPersonaTools(ctx, marvinDir) {
17535
+ return [
17536
+ tool17(
17537
+ "set_persona",
17538
+ "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
+ {
17540
+ persona: external_exports.string().describe(
17541
+ 'Persona ID or short name (e.g. "po", "product-owner", "dm", "delivery-manager", "tl", "tech-lead")'
17542
+ )
17543
+ },
17544
+ async (args) => {
17545
+ const resolved = ctx.setPersona(args.persona);
17546
+ if (!resolved) {
17547
+ const summaries = buildPersonaSummaries();
17548
+ return {
17549
+ content: [
17550
+ {
17551
+ type: "text",
17552
+ text: `Unknown persona "${args.persona}".
17553
+
17554
+ ${summaries}`
17555
+ }
17556
+ ],
17557
+ isError: true
17558
+ };
17559
+ }
17560
+ const guidance = buildMcpGuidance(resolved, marvinDir);
17561
+ return {
17562
+ content: [{ type: "text", text: guidance }]
17563
+ };
17564
+ }
17565
+ ),
17566
+ tool17(
17567
+ "get_persona_guidance",
17568
+ "Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
17569
+ {
17570
+ persona: external_exports.string().optional().describe(
17571
+ "Optional persona ID or short name. Omit to list all personas."
17572
+ )
17573
+ },
17574
+ async (args) => {
17575
+ if (!args.persona) {
17576
+ const summaries = buildPersonaSummaries();
17577
+ return {
17578
+ content: [{ type: "text", text: summaries }]
17579
+ };
17580
+ }
17581
+ const resolved = getPersona(args.persona);
17582
+ if (!resolved) {
17583
+ const summaries = buildPersonaSummaries();
17584
+ return {
17585
+ content: [
17586
+ {
17587
+ type: "text",
17588
+ text: `Unknown persona "${args.persona}".
17589
+
17590
+ ${summaries}`
17591
+ }
17592
+ ],
17593
+ isError: true
17594
+ };
17595
+ }
17596
+ const guidance = buildMcpGuidance(resolved, marvinDir);
17597
+ return {
17598
+ content: [{ type: "text", text: guidance }]
17599
+ };
17600
+ },
17601
+ { annotations: { readOnly: true } }
17602
+ )
17603
+ ];
17604
+ }
17605
+
17606
+ // src/mcp/tool-wrapper.ts
17607
+ function extractDocType(toolName) {
17608
+ if (toolName === "save_report") return "report";
17609
+ const match = toolName.match(/^(?:create|update)_(\w+)$/);
17610
+ return match ? match[1] : void 0;
17611
+ }
17612
+ function wrapToolsWithPersonaValidation(tools, ctx) {
17613
+ return tools.map((t) => {
17614
+ const docType = extractDocType(t.name);
17615
+ if (!docType) return t;
17616
+ return {
17617
+ ...t,
17618
+ handler: async (args, extra) => {
17619
+ const persona = ctx.getActivePersona();
17620
+ if (!persona) {
17621
+ const summaries = buildPersonaSummaries();
17622
+ return {
17623
+ content: [
17624
+ {
17625
+ type: "text",
17626
+ text: `[PERSONA REQUIRED] You must set an active persona before creating or updating documents. Call the set_persona tool first.
17627
+
17628
+ ${summaries}`
17629
+ }
17630
+ ],
17631
+ isError: true
17632
+ };
17633
+ }
17634
+ const result = await t.handler(args, extra);
17635
+ if (ctx.isDocumentTypeAllowed(docType)) {
17636
+ return result;
17637
+ }
17638
+ const warning = [
17639
+ `[PERSONA WARNING] You are acting as ${persona.name} (${persona.shortName}). Creating/updating "${docType}" documents is outside your typical scope.`,
17640
+ "",
17641
+ `Your allowed document types: ${persona.documentTypes.join(", ")}`,
17642
+ "",
17643
+ "Consider whether this is the right persona for this task, or switch with set_persona.",
17644
+ "",
17645
+ "---"
17646
+ ].join("\n");
17647
+ const content = Array.isArray(result.content) ? result.content.map(
17648
+ (block, index) => {
17649
+ if (index === 0 && block.type === "text" && block.text) {
17650
+ return { ...block, text: `${warning}
17651
+ ${block.text}` };
17652
+ }
17653
+ return block;
17654
+ }
17655
+ ) : result.content;
17656
+ return { ...result, content };
17657
+ }
17658
+ };
17659
+ });
17660
+ }
17661
+
17455
17662
  // src/mcp/stdio-server.ts
17456
17663
  function collectTools(marvinDir) {
17457
17664
  const config2 = loadProjectConfig(marvinDir);
@@ -17514,8 +17721,11 @@ async function startStdioServer(options) {
17514
17721
  { name: "marvin-governance", version: "0.1.0" },
17515
17722
  { capabilities: { tools: {} } }
17516
17723
  );
17517
- const tools = collectTools(options.marvinDir);
17518
- registerSdkTools(server, tools);
17724
+ const contextManager = new PersonaContextManager();
17725
+ const governanceTools = collectTools(options.marvinDir);
17726
+ const personaTools = createPersonaTools(contextManager, options.marvinDir);
17727
+ const wrappedTools = wrapToolsWithPersonaValidation(governanceTools, contextManager);
17728
+ registerSdkTools(server, [...personaTools, ...wrappedTools]);
17519
17729
  const transport = new StdioServerTransport();
17520
17730
  await server.connect(transport);
17521
17731
  }
@@ -19482,7 +19692,7 @@ function createProgram() {
19482
19692
  const program = new Command();
19483
19693
  program.name("marvin").description(
19484
19694
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
19485
- ).version("0.2.0");
19695
+ ).version("0.2.1");
19486
19696
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
19487
19697
  await initCommand();
19488
19698
  });