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/marvin.js CHANGED
@@ -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
  }