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 +212 -2
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +371 -2
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +212 -2
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin-serve.js
CHANGED
|
@@ -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
|
}
|