mrvn-cli 0.2.0 → 0.2.1

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
@@ -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
  }