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.
@@ -208,7 +208,7 @@ var DocumentStore = class {
208
208
  updated: now,
209
209
  ...cleaned
210
210
  };
211
- const fileName = type === "meeting" ? `${now.slice(0, 10)}-${slugify(fullFrontmatter.title)}.md` : `${id}.md`;
211
+ const fileName = type === "meeting" ? `${cleaned.date?.slice(0, 10) ?? now.slice(0, 10)}-${slugify(fullFrontmatter.title)}.md` : `${id}.md`;
212
212
  const filePath = path3.join(dir, fileName);
213
213
  const doc = {
214
214
  frontmatter: fullFrontmatter,
@@ -232,7 +232,7 @@ var DocumentStore = class {
232
232
  }
233
233
  const dir = path3.join(this.docsDir, dirName);
234
234
  fs3.mkdirSync(dir, { recursive: true });
235
- const fileName = type === "meeting" ? `${frontmatter.created.slice(0, 10)}-${slugify(frontmatter.title)}.md` : `${frontmatter.id}.md`;
235
+ const fileName = type === "meeting" ? `${frontmatter.date?.slice(0, 10) ?? frontmatter.created.slice(0, 10)}-${slugify(frontmatter.title)}.md` : `${frontmatter.id}.md`;
236
236
  const filePath = path3.join(dir, fileName);
237
237
  const doc = { frontmatter, content, filePath };
238
238
  fs3.writeFileSync(filePath, serializeDocument(doc), "utf-8");
@@ -14851,7 +14851,7 @@ function createMeetingTools(store) {
14851
14851
  owner: external_exports.string().optional().describe("Meeting organizer"),
14852
14852
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
14853
14853
  attendees: external_exports.array(external_exports.string()).optional().describe("List of attendees"),
14854
- date: external_exports.string().optional().describe("Meeting date (ISO format)")
14854
+ 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.")
14855
14855
  },
14856
14856
  async (args) => {
14857
14857
  const frontmatter = {
@@ -14861,7 +14861,7 @@ function createMeetingTools(store) {
14861
14861
  if (args.owner) frontmatter.owner = args.owner;
14862
14862
  if (args.tags) frontmatter.tags = args.tags;
14863
14863
  if (args.attendees) frontmatter.attendees = args.attendees;
14864
- if (args.date) frontmatter.date = args.date;
14864
+ frontmatter.date = args.date;
14865
14865
  const doc = store.create(
14866
14866
  "meeting",
14867
14867
  frontmatter,
@@ -15561,7 +15561,7 @@ var genericAgilePlugin = {
15561
15561
 
15562
15562
  **Meeting Tools:**
15563
15563
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15564
- - **create_meeting**: Record new meetings with attendees, date, and agenda.
15564
+ - **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.
15565
15565
  - **update_meeting**: Update meeting status or notes after completion.
15566
15566
  - **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
15567
15567
 
@@ -15582,7 +15582,7 @@ var genericAgilePlugin = {
15582
15582
 
15583
15583
  **Meeting Tools:**
15584
15584
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15585
- - **create_meeting**: Record new meetings with attendees, date, and agenda.
15585
+ - **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.
15586
15586
  - **update_meeting**: Update meeting status or notes after completion.
15587
15587
  - **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
15588
15588
 
@@ -15610,7 +15610,7 @@ var genericAgilePlugin = {
15610
15610
 
15611
15611
  **Meeting Tools:**
15612
15612
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15613
- - **create_meeting**: Record new meetings with attendees, date, and agenda.
15613
+ - **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.
15614
15614
  - **update_meeting**: Update meeting status or notes after completion.
15615
15615
  - **analyze_meeting**: Analyze a completed meeting to extract decisions, actions, and questions. Use this to ensure meeting outcomes are properly tracked as governance artifacts.
15616
15616
 
@@ -15628,7 +15628,7 @@ var genericAgilePlugin = {
15628
15628
  **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.
15629
15629
 
15630
15630
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15631
- - **create_meeting**: Record meetings with attendees, date, and agenda.
15631
+ - **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.
15632
15632
  - **update_meeting**: Update meeting status or notes.
15633
15633
  - **analyze_meeting**: Analyze a meeting to extract decisions, actions, and questions as governance artifacts.`
15634
15634
  }
@@ -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
  }