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.
@@ -16530,6 +16530,10 @@ function resolvePlugin(methodologyId) {
16530
16530
  function getPluginTools(plugin, store, marvinDir) {
16531
16531
  return plugin.tools ? plugin.tools(store, marvinDir) : [];
16532
16532
  }
16533
+ function getPluginPromptFragment(plugin, personaId) {
16534
+ if (!plugin.promptFragments) return void 0;
16535
+ return plugin.promptFragments[personaId] ?? plugin.promptFragments["*"];
16536
+ }
16533
16537
 
16534
16538
  // src/skills/registry.ts
16535
16539
  import * as fs7 from "fs";
@@ -16697,6 +16701,18 @@ function loadAllSkills(marvinDir) {
16697
16701
  }
16698
16702
  return skills;
16699
16703
  }
16704
+ function resolveSkillsForPersona(personaId, skillsConfig, allSkills) {
16705
+ if (skillsConfig?.[personaId]) {
16706
+ return skillsConfig[personaId].filter((id) => allSkills.has(id));
16707
+ }
16708
+ const result = [];
16709
+ for (const [id, skill] of allSkills) {
16710
+ if (skill.personas?.includes(personaId)) {
16711
+ result.push(id);
16712
+ }
16713
+ }
16714
+ return result;
16715
+ }
16700
16716
  function getSkillTools(skillIds, allSkills, store) {
16701
16717
  const tools = [];
16702
16718
  for (const id of skillIds) {
@@ -16707,6 +16723,19 @@ function getSkillTools(skillIds, allSkills, store) {
16707
16723
  }
16708
16724
  return tools;
16709
16725
  }
16726
+ function getSkillPromptFragment(skillIds, allSkills, personaId) {
16727
+ const fragments = [];
16728
+ for (const id of skillIds) {
16729
+ const skill = allSkills.get(id);
16730
+ if (!skill?.promptFragments) continue;
16731
+ const fragment = skill.promptFragments[personaId] ?? skill.promptFragments["*"];
16732
+ if (fragment) {
16733
+ fragments.push(`### ${skill.name}
16734
+ ${fragment}`);
16735
+ }
16736
+ }
16737
+ return fragments.length > 0 ? fragments.join("\n\n") : void 0;
16738
+ }
16710
16739
 
16711
16740
  // src/skills/action-tools.ts
16712
16741
  import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
@@ -16813,6 +16842,343 @@ function createSkillActionTools(skills, context) {
16813
16842
  return tools;
16814
16843
  }
16815
16844
 
16845
+ // src/personas/builtin/product-owner.ts
16846
+ var productOwner = {
16847
+ id: "product-owner",
16848
+ name: "Product Owner",
16849
+ shortName: "po",
16850
+ description: "Focuses on product vision, stakeholder needs, backlog prioritization, and value delivery.",
16851
+ systemPrompt: `You are Marvin, acting as a **Product Owner**. Your role is to help the team maximize the value delivered by the product.
16852
+
16853
+ ## Core Responsibilities
16854
+ - Define and communicate the product vision and strategy
16855
+ - Manage and prioritize the product backlog
16856
+ - Ensure stakeholder needs are understood and addressed
16857
+ - Make decisions about scope, priority, and trade-offs
16858
+ - Accept or reject work results based on acceptance criteria
16859
+
16860
+ ## How You Work
16861
+ - Ask clarifying questions to understand business value and user needs
16862
+ - Create and refine decisions (D-xxx) for important product choices
16863
+ - Track questions (Q-xxx) that need stakeholder input
16864
+ - Define acceptance criteria for features and deliverables
16865
+ - Prioritize actions (A-xxx) based on business value
16866
+
16867
+ ## Communication Style
16868
+ - Business-oriented language, avoid unnecessary technical jargon
16869
+ - Focus on outcomes and value, not implementation details
16870
+ - Be decisive but transparent about trade-offs
16871
+ - Challenge assumptions that don't align with product goals`,
16872
+ focusAreas: [
16873
+ "Product vision and strategy",
16874
+ "Backlog management",
16875
+ "Stakeholder communication",
16876
+ "Value delivery",
16877
+ "Acceptance criteria",
16878
+ "Feature definition and prioritization"
16879
+ ],
16880
+ documentTypes: ["decision", "question", "action", "feature"]
16881
+ };
16882
+
16883
+ // src/personas/builtin/delivery-manager.ts
16884
+ var deliveryManager = {
16885
+ id: "delivery-manager",
16886
+ name: "Delivery Manager",
16887
+ shortName: "dm",
16888
+ description: "Focuses on project delivery, risk management, team coordination, and process governance.",
16889
+ systemPrompt: `You are Marvin, acting as a **Delivery Manager**. Your role is to ensure the project is delivered on time, within scope, and with managed risks.
16890
+
16891
+ ## Core Responsibilities
16892
+ - Track project progress and identify blockers
16893
+ - Manage risks, issues, and dependencies
16894
+ - Coordinate between team members and stakeholders
16895
+ - Ensure governance processes are followed (decisions logged, actions tracked)
16896
+ - Facilitate meetings and ensure outcomes are captured
16897
+
16898
+ ## How You Work
16899
+ - Review open actions (A-xxx) and follow up on overdue items
16900
+ - Ensure decisions (D-xxx) are properly documented with rationale
16901
+ - Track questions (Q-xxx) and ensure they get answered
16902
+ - Monitor project health and flag risks early
16903
+ - Create meeting notes and ensure action items are assigned
16904
+
16905
+ ## Communication Style
16906
+ - Process-oriented but pragmatic
16907
+ - Focus on status, risks, and blockers
16908
+ - Be proactive about follow-ups and deadlines
16909
+ - Keep stakeholders informed with concise updates`,
16910
+ focusAreas: [
16911
+ "Project delivery",
16912
+ "Risk management",
16913
+ "Team coordination",
16914
+ "Process governance",
16915
+ "Status tracking",
16916
+ "Epic scheduling and tracking"
16917
+ ],
16918
+ documentTypes: ["action", "decision", "meeting", "question", "feature", "epic"]
16919
+ };
16920
+
16921
+ // src/personas/builtin/tech-lead.ts
16922
+ var techLead = {
16923
+ id: "tech-lead",
16924
+ name: "Technical Lead",
16925
+ shortName: "tl",
16926
+ description: "Focuses on technical architecture, code quality, technical decisions, and implementation guidance.",
16927
+ systemPrompt: `You are Marvin, acting as a **Technical Lead**. Your role is to guide the team on technical decisions and ensure high-quality implementation.
16928
+
16929
+ ## Core Responsibilities
16930
+ - Define and maintain technical architecture
16931
+ - Make and document technical decisions with clear rationale
16932
+ - Review technical approaches and identify potential issues
16933
+ - Guide the team on best practices and patterns
16934
+ - Evaluate technical risks and propose mitigations
16935
+
16936
+ ## How You Work
16937
+ - Create decisions (D-xxx) for significant technical choices (framework, architecture, patterns)
16938
+ - Document technical questions (Q-xxx) that need investigation or proof-of-concept
16939
+ - Define technical actions (A-xxx) for implementation tasks
16940
+ - Consider non-functional requirements (performance, security, maintainability)
16941
+ - Provide clear technical guidance with examples when helpful
16942
+
16943
+ ## Communication Style
16944
+ - Technical but accessible \u2014 explain complex concepts clearly
16945
+ - Evidence-based decision making with documented trade-offs
16946
+ - Pragmatic about technical debt vs. delivery speed
16947
+ - Focus on maintainability and long-term sustainability`,
16948
+ focusAreas: [
16949
+ "Technical architecture",
16950
+ "Code quality",
16951
+ "Technical decisions",
16952
+ "Implementation guidance",
16953
+ "Non-functional requirements",
16954
+ "Epic creation and scoping"
16955
+ ],
16956
+ documentTypes: ["decision", "action", "question", "epic"]
16957
+ };
16958
+
16959
+ // src/personas/registry.ts
16960
+ var BUILTIN_PERSONAS = [
16961
+ productOwner,
16962
+ deliveryManager,
16963
+ techLead
16964
+ ];
16965
+ function getPersona(idOrShortName) {
16966
+ const key = idOrShortName.toLowerCase();
16967
+ return BUILTIN_PERSONAS.find(
16968
+ (p) => p.id === key || p.shortName === key
16969
+ );
16970
+ }
16971
+ function listPersonas() {
16972
+ return [...BUILTIN_PERSONAS];
16973
+ }
16974
+
16975
+ // src/mcp/persona-context.ts
16976
+ var PersonaContextManager = class {
16977
+ activePersona = null;
16978
+ setPersona(idOrShortName) {
16979
+ const persona = getPersona(idOrShortName);
16980
+ if (persona) {
16981
+ this.activePersona = persona;
16982
+ }
16983
+ return persona;
16984
+ }
16985
+ getActivePersona() {
16986
+ return this.activePersona;
16987
+ }
16988
+ clearPersona() {
16989
+ this.activePersona = null;
16990
+ }
16991
+ isDocumentTypeAllowed(docType) {
16992
+ if (!this.activePersona) return true;
16993
+ return this.activePersona.documentTypes.includes(docType);
16994
+ }
16995
+ };
16996
+ function buildMcpGuidance(persona, marvinDir) {
16997
+ const parts = [];
16998
+ parts.push(`# Active Persona: ${persona.name} (${persona.shortName})`);
16999
+ parts.push(`
17000
+ ${persona.description}`);
17001
+ parts.push(`
17002
+ ## Focus Areas`);
17003
+ for (const area of persona.focusAreas) {
17004
+ parts.push(`- ${area}`);
17005
+ }
17006
+ parts.push(`
17007
+ ## Allowed Document Types`);
17008
+ parts.push(persona.documentTypes.join(", "));
17009
+ parts.push(`
17010
+ ## Behavioral Instructions`);
17011
+ parts.push(persona.systemPrompt);
17012
+ try {
17013
+ const config2 = loadProjectConfig(marvinDir);
17014
+ const plugin = resolvePlugin(config2.methodology);
17015
+ if (plugin) {
17016
+ const fragment = getPluginPromptFragment(plugin, persona.id);
17017
+ if (fragment) {
17018
+ parts.push(`
17019
+ ## Plugin Rules`);
17020
+ parts.push(fragment);
17021
+ }
17022
+ }
17023
+ const allSkills = loadAllSkills(marvinDir);
17024
+ const skillIds = resolveSkillsForPersona(
17025
+ persona.id,
17026
+ config2.skills,
17027
+ allSkills
17028
+ );
17029
+ if (skillIds.length > 0) {
17030
+ const fragment = getSkillPromptFragment(skillIds, allSkills, persona.id);
17031
+ if (fragment) {
17032
+ parts.push(`
17033
+ ## Skill Rules`);
17034
+ parts.push(fragment);
17035
+ }
17036
+ }
17037
+ } catch {
17038
+ }
17039
+ return parts.join("\n");
17040
+ }
17041
+ function buildPersonaSummaries() {
17042
+ const personas = listPersonas();
17043
+ const lines = personas.map(
17044
+ (p) => `- **${p.name}** (${p.shortName}): ${p.description}
17045
+ Document types: ${p.documentTypes.join(", ")}`
17046
+ );
17047
+ return `# Available Personas
17048
+
17049
+ ${lines.join("\n\n")}`;
17050
+ }
17051
+
17052
+ // src/mcp/persona-tools.ts
17053
+ import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
17054
+ function createPersonaTools(ctx, marvinDir) {
17055
+ return [
17056
+ tool17(
17057
+ "set_persona",
17058
+ "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.",
17059
+ {
17060
+ persona: external_exports.string().describe(
17061
+ 'Persona ID or short name (e.g. "po", "product-owner", "dm", "delivery-manager", "tl", "tech-lead")'
17062
+ )
17063
+ },
17064
+ async (args) => {
17065
+ const resolved = ctx.setPersona(args.persona);
17066
+ if (!resolved) {
17067
+ const summaries = buildPersonaSummaries();
17068
+ return {
17069
+ content: [
17070
+ {
17071
+ type: "text",
17072
+ text: `Unknown persona "${args.persona}".
17073
+
17074
+ ${summaries}`
17075
+ }
17076
+ ],
17077
+ isError: true
17078
+ };
17079
+ }
17080
+ const guidance = buildMcpGuidance(resolved, marvinDir);
17081
+ return {
17082
+ content: [{ type: "text", text: guidance }]
17083
+ };
17084
+ }
17085
+ ),
17086
+ tool17(
17087
+ "get_persona_guidance",
17088
+ "Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
17089
+ {
17090
+ persona: external_exports.string().optional().describe(
17091
+ "Optional persona ID or short name. Omit to list all personas."
17092
+ )
17093
+ },
17094
+ async (args) => {
17095
+ if (!args.persona) {
17096
+ const summaries = buildPersonaSummaries();
17097
+ return {
17098
+ content: [{ type: "text", text: summaries }]
17099
+ };
17100
+ }
17101
+ const resolved = getPersona(args.persona);
17102
+ if (!resolved) {
17103
+ const summaries = buildPersonaSummaries();
17104
+ return {
17105
+ content: [
17106
+ {
17107
+ type: "text",
17108
+ text: `Unknown persona "${args.persona}".
17109
+
17110
+ ${summaries}`
17111
+ }
17112
+ ],
17113
+ isError: true
17114
+ };
17115
+ }
17116
+ const guidance = buildMcpGuidance(resolved, marvinDir);
17117
+ return {
17118
+ content: [{ type: "text", text: guidance }]
17119
+ };
17120
+ },
17121
+ { annotations: { readOnly: true } }
17122
+ )
17123
+ ];
17124
+ }
17125
+
17126
+ // src/mcp/tool-wrapper.ts
17127
+ function extractDocType(toolName) {
17128
+ if (toolName === "save_report") return "report";
17129
+ const match = toolName.match(/^(?:create|update)_(\w+)$/);
17130
+ return match ? match[1] : void 0;
17131
+ }
17132
+ function wrapToolsWithPersonaValidation(tools, ctx) {
17133
+ return tools.map((t) => {
17134
+ const docType = extractDocType(t.name);
17135
+ if (!docType) return t;
17136
+ return {
17137
+ ...t,
17138
+ handler: async (args, extra) => {
17139
+ const persona = ctx.getActivePersona();
17140
+ if (!persona) {
17141
+ const summaries = buildPersonaSummaries();
17142
+ return {
17143
+ content: [
17144
+ {
17145
+ type: "text",
17146
+ text: `[PERSONA REQUIRED] You must set an active persona before creating or updating documents. Call the set_persona tool first.
17147
+
17148
+ ${summaries}`
17149
+ }
17150
+ ],
17151
+ isError: true
17152
+ };
17153
+ }
17154
+ const result = await t.handler(args, extra);
17155
+ if (ctx.isDocumentTypeAllowed(docType)) {
17156
+ return result;
17157
+ }
17158
+ const warning = [
17159
+ `[PERSONA WARNING] You are acting as ${persona.name} (${persona.shortName}). Creating/updating "${docType}" documents is outside your typical scope.`,
17160
+ "",
17161
+ `Your allowed document types: ${persona.documentTypes.join(", ")}`,
17162
+ "",
17163
+ "Consider whether this is the right persona for this task, or switch with set_persona.",
17164
+ "",
17165
+ "---"
17166
+ ].join("\n");
17167
+ const content = Array.isArray(result.content) ? result.content.map(
17168
+ (block, index) => {
17169
+ if (index === 0 && block.type === "text" && block.text) {
17170
+ return { ...block, text: `${warning}
17171
+ ${block.text}` };
17172
+ }
17173
+ return block;
17174
+ }
17175
+ ) : result.content;
17176
+ return { ...result, content };
17177
+ }
17178
+ };
17179
+ });
17180
+ }
17181
+
16816
17182
  // src/mcp/stdio-server.ts
16817
17183
  function collectTools(marvinDir) {
16818
17184
  const config2 = loadProjectConfig(marvinDir);
@@ -16875,8 +17241,11 @@ async function startStdioServer(options) {
16875
17241
  { name: "marvin-governance", version: "0.1.0" },
16876
17242
  { capabilities: { tools: {} } }
16877
17243
  );
16878
- const tools = collectTools(options.marvinDir);
16879
- registerSdkTools(server, tools);
17244
+ const contextManager = new PersonaContextManager();
17245
+ const governanceTools = collectTools(options.marvinDir);
17246
+ const personaTools = createPersonaTools(contextManager, options.marvinDir);
17247
+ const wrappedTools = wrapToolsWithPersonaValidation(governanceTools, contextManager);
17248
+ registerSdkTools(server, [...personaTools, ...wrappedTools]);
16880
17249
  const transport = new StdioServerTransport();
16881
17250
  await server.connect(transport);
16882
17251
  }