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/marvin.js CHANGED
@@ -13981,7 +13981,7 @@ function createMeetingTools(store) {
13981
13981
  owner: external_exports.string().optional().describe("Meeting organizer"),
13982
13982
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
13983
13983
  attendees: external_exports.array(external_exports.string()).optional().describe("List of attendees"),
13984
- date: external_exports.string().optional().describe("Meeting date (ISO format)")
13984
+ 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.")
13985
13985
  },
13986
13986
  async (args) => {
13987
13987
  const frontmatter = {
@@ -13991,7 +13991,7 @@ function createMeetingTools(store) {
13991
13991
  if (args.owner) frontmatter.owner = args.owner;
13992
13992
  if (args.tags) frontmatter.tags = args.tags;
13993
13993
  if (args.attendees) frontmatter.attendees = args.attendees;
13994
- if (args.date) frontmatter.date = args.date;
13994
+ frontmatter.date = args.date;
13995
13995
  const doc = store.create(
13996
13996
  "meeting",
13997
13997
  frontmatter,
@@ -14691,7 +14691,7 @@ var genericAgilePlugin = {
14691
14691
 
14692
14692
  **Meeting Tools:**
14693
14693
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
14694
- - **create_meeting**: Record new meetings with attendees, date, and agenda.
14694
+ - **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.
14695
14695
  - **update_meeting**: Update meeting status or notes after completion.
14696
14696
  - **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
14697
14697
 
@@ -14712,7 +14712,7 @@ var genericAgilePlugin = {
14712
14712
 
14713
14713
  **Meeting Tools:**
14714
14714
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
14715
- - **create_meeting**: Record new meetings with attendees, date, and agenda.
14715
+ - **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.
14716
14716
  - **update_meeting**: Update meeting status or notes after completion.
14717
14717
  - **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
14718
14718
 
@@ -14740,7 +14740,7 @@ var genericAgilePlugin = {
14740
14740
 
14741
14741
  **Meeting Tools:**
14742
14742
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
14743
- - **create_meeting**: Record new meetings with attendees, date, and agenda.
14743
+ - **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.
14744
14744
  - **update_meeting**: Update meeting status or notes after completion.
14745
14745
  - **analyze_meeting**: Analyze a completed meeting to extract decisions, actions, and questions. Use this to ensure meeting outcomes are properly tracked as governance artifacts.
14746
14746
 
@@ -14758,7 +14758,7 @@ var genericAgilePlugin = {
14758
14758
  **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.
14759
14759
 
14760
14760
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
14761
- - **create_meeting**: Record meetings with attendees, date, and agenda.
14761
+ - **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.
14762
14762
  - **update_meeting**: Update meeting status or notes.
14763
14763
  - **analyze_meeting**: Analyze a meeting to extract decisions, actions, and questions as governance artifacts.`
14764
14764
  }
@@ -16103,7 +16103,7 @@ var DocumentStore = class {
16103
16103
  updated: now,
16104
16104
  ...cleaned
16105
16105
  };
16106
- const fileName = type === "meeting" ? `${now.slice(0, 10)}-${slugify2(fullFrontmatter.title)}.md` : `${id}.md`;
16106
+ const fileName = type === "meeting" ? `${cleaned.date?.slice(0, 10) ?? now.slice(0, 10)}-${slugify2(fullFrontmatter.title)}.md` : `${id}.md`;
16107
16107
  const filePath = path5.join(dir, fileName);
16108
16108
  const doc = {
16109
16109
  frontmatter: fullFrontmatter,
@@ -16127,7 +16127,7 @@ var DocumentStore = class {
16127
16127
  }
16128
16128
  const dir = path5.join(this.docsDir, dirName);
16129
16129
  fs5.mkdirSync(dir, { recursive: true });
16130
- const fileName = type === "meeting" ? `${frontmatter.created.slice(0, 10)}-${slugify2(frontmatter.title)}.md` : `${frontmatter.id}.md`;
16130
+ const fileName = type === "meeting" ? `${frontmatter.date?.slice(0, 10) ?? frontmatter.created.slice(0, 10)}-${slugify2(frontmatter.title)}.md` : `${frontmatter.id}.md`;
16131
16131
  const filePath = path5.join(dir, fileName);
16132
16132
  const doc = { frontmatter, content, filePath };
16133
16133
  fs5.writeFileSync(filePath, serializeDocument(doc), "utf-8");
@@ -18458,6 +18458,213 @@ function createSkillActionTools(skills, context) {
18458
18458
  return tools;
18459
18459
  }
18460
18460
 
18461
+ // src/mcp/persona-context.ts
18462
+ var PersonaContextManager = class {
18463
+ activePersona = null;
18464
+ setPersona(idOrShortName) {
18465
+ const persona = getPersona(idOrShortName);
18466
+ if (persona) {
18467
+ this.activePersona = persona;
18468
+ }
18469
+ return persona;
18470
+ }
18471
+ getActivePersona() {
18472
+ return this.activePersona;
18473
+ }
18474
+ clearPersona() {
18475
+ this.activePersona = null;
18476
+ }
18477
+ isDocumentTypeAllowed(docType) {
18478
+ if (!this.activePersona) return true;
18479
+ return this.activePersona.documentTypes.includes(docType);
18480
+ }
18481
+ };
18482
+ function buildMcpGuidance(persona, marvinDir) {
18483
+ const parts = [];
18484
+ parts.push(`# Active Persona: ${persona.name} (${persona.shortName})`);
18485
+ parts.push(`
18486
+ ${persona.description}`);
18487
+ parts.push(`
18488
+ ## Focus Areas`);
18489
+ for (const area of persona.focusAreas) {
18490
+ parts.push(`- ${area}`);
18491
+ }
18492
+ parts.push(`
18493
+ ## Allowed Document Types`);
18494
+ parts.push(persona.documentTypes.join(", "));
18495
+ parts.push(`
18496
+ ## Behavioral Instructions`);
18497
+ parts.push(persona.systemPrompt);
18498
+ try {
18499
+ const config2 = loadProjectConfig(marvinDir);
18500
+ const plugin = resolvePlugin(config2.methodology);
18501
+ if (plugin) {
18502
+ const fragment = getPluginPromptFragment(plugin, persona.id);
18503
+ if (fragment) {
18504
+ parts.push(`
18505
+ ## Plugin Rules`);
18506
+ parts.push(fragment);
18507
+ }
18508
+ }
18509
+ const allSkills = loadAllSkills(marvinDir);
18510
+ const skillIds = resolveSkillsForPersona(
18511
+ persona.id,
18512
+ config2.skills,
18513
+ allSkills
18514
+ );
18515
+ if (skillIds.length > 0) {
18516
+ const fragment = getSkillPromptFragment(skillIds, allSkills, persona.id);
18517
+ if (fragment) {
18518
+ parts.push(`
18519
+ ## Skill Rules`);
18520
+ parts.push(fragment);
18521
+ }
18522
+ }
18523
+ } catch {
18524
+ }
18525
+ return parts.join("\n");
18526
+ }
18527
+ function buildPersonaSummaries() {
18528
+ const personas = listPersonas();
18529
+ const lines = personas.map(
18530
+ (p) => `- **${p.name}** (${p.shortName}): ${p.description}
18531
+ Document types: ${p.documentTypes.join(", ")}`
18532
+ );
18533
+ return `# Available Personas
18534
+
18535
+ ${lines.join("\n\n")}`;
18536
+ }
18537
+
18538
+ // src/mcp/persona-tools.ts
18539
+ import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
18540
+ function createPersonaTools(ctx, marvinDir) {
18541
+ return [
18542
+ tool17(
18543
+ "set_persona",
18544
+ "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.",
18545
+ {
18546
+ persona: external_exports.string().describe(
18547
+ 'Persona ID or short name (e.g. "po", "product-owner", "dm", "delivery-manager", "tl", "tech-lead")'
18548
+ )
18549
+ },
18550
+ async (args) => {
18551
+ const resolved = ctx.setPersona(args.persona);
18552
+ if (!resolved) {
18553
+ const summaries = buildPersonaSummaries();
18554
+ return {
18555
+ content: [
18556
+ {
18557
+ type: "text",
18558
+ text: `Unknown persona "${args.persona}".
18559
+
18560
+ ${summaries}`
18561
+ }
18562
+ ],
18563
+ isError: true
18564
+ };
18565
+ }
18566
+ const guidance = buildMcpGuidance(resolved, marvinDir);
18567
+ return {
18568
+ content: [{ type: "text", text: guidance }]
18569
+ };
18570
+ }
18571
+ ),
18572
+ tool17(
18573
+ "get_persona_guidance",
18574
+ "Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
18575
+ {
18576
+ persona: external_exports.string().optional().describe(
18577
+ "Optional persona ID or short name. Omit to list all personas."
18578
+ )
18579
+ },
18580
+ async (args) => {
18581
+ if (!args.persona) {
18582
+ const summaries = buildPersonaSummaries();
18583
+ return {
18584
+ content: [{ type: "text", text: summaries }]
18585
+ };
18586
+ }
18587
+ const resolved = getPersona(args.persona);
18588
+ if (!resolved) {
18589
+ const summaries = buildPersonaSummaries();
18590
+ return {
18591
+ content: [
18592
+ {
18593
+ type: "text",
18594
+ text: `Unknown persona "${args.persona}".
18595
+
18596
+ ${summaries}`
18597
+ }
18598
+ ],
18599
+ isError: true
18600
+ };
18601
+ }
18602
+ const guidance = buildMcpGuidance(resolved, marvinDir);
18603
+ return {
18604
+ content: [{ type: "text", text: guidance }]
18605
+ };
18606
+ },
18607
+ { annotations: { readOnly: true } }
18608
+ )
18609
+ ];
18610
+ }
18611
+
18612
+ // src/mcp/tool-wrapper.ts
18613
+ function extractDocType(toolName) {
18614
+ if (toolName === "save_report") return "report";
18615
+ const match = toolName.match(/^(?:create|update)_(\w+)$/);
18616
+ return match ? match[1] : void 0;
18617
+ }
18618
+ function wrapToolsWithPersonaValidation(tools, ctx) {
18619
+ return tools.map((t) => {
18620
+ const docType = extractDocType(t.name);
18621
+ if (!docType) return t;
18622
+ return {
18623
+ ...t,
18624
+ handler: async (args, extra) => {
18625
+ const persona = ctx.getActivePersona();
18626
+ if (!persona) {
18627
+ const summaries = buildPersonaSummaries();
18628
+ return {
18629
+ content: [
18630
+ {
18631
+ type: "text",
18632
+ text: `[PERSONA REQUIRED] You must set an active persona before creating or updating documents. Call the set_persona tool first.
18633
+
18634
+ ${summaries}`
18635
+ }
18636
+ ],
18637
+ isError: true
18638
+ };
18639
+ }
18640
+ const result = await t.handler(args, extra);
18641
+ if (ctx.isDocumentTypeAllowed(docType)) {
18642
+ return result;
18643
+ }
18644
+ const warning = [
18645
+ `[PERSONA WARNING] You are acting as ${persona.name} (${persona.shortName}). Creating/updating "${docType}" documents is outside your typical scope.`,
18646
+ "",
18647
+ `Your allowed document types: ${persona.documentTypes.join(", ")}`,
18648
+ "",
18649
+ "Consider whether this is the right persona for this task, or switch with set_persona.",
18650
+ "",
18651
+ "---"
18652
+ ].join("\n");
18653
+ const content = Array.isArray(result.content) ? result.content.map(
18654
+ (block, index) => {
18655
+ if (index === 0 && block.type === "text" && block.text) {
18656
+ return { ...block, text: `${warning}
18657
+ ${block.text}` };
18658
+ }
18659
+ return block;
18660
+ }
18661
+ ) : result.content;
18662
+ return { ...result, content };
18663
+ }
18664
+ };
18665
+ });
18666
+ }
18667
+
18461
18668
  // src/mcp/stdio-server.ts
18462
18669
  function collectTools(marvinDir) {
18463
18670
  const config2 = loadProjectConfig(marvinDir);
@@ -18520,8 +18727,11 @@ async function startStdioServer(options) {
18520
18727
  { name: "marvin-governance", version: "0.1.0" },
18521
18728
  { capabilities: { tools: {} } }
18522
18729
  );
18523
- const tools = collectTools(options.marvinDir);
18524
- registerSdkTools(server, tools);
18730
+ const contextManager = new PersonaContextManager();
18731
+ const governanceTools = collectTools(options.marvinDir);
18732
+ const personaTools = createPersonaTools(contextManager, options.marvinDir);
18733
+ const wrappedTools = wrapToolsWithPersonaValidation(governanceTools, contextManager);
18734
+ registerSdkTools(server, [...personaTools, ...wrappedTools]);
18525
18735
  const transport = new StdioServerTransport();
18526
18736
  await server.connect(transport);
18527
18737
  }
@@ -19476,7 +19686,7 @@ function createProgram() {
19476
19686
  const program2 = new Command();
19477
19687
  program2.name("marvin").description(
19478
19688
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
19479
- ).version("0.2.0");
19689
+ ).version("0.2.1");
19480
19690
  program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
19481
19691
  await initCommand();
19482
19692
  });