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/index.js +221 -11
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +379 -10
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +221 -11
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin-serve.js
CHANGED
|
@@ -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().
|
|
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
|
-
|
|
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
|
|
16879
|
-
|
|
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
|
}
|