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/index.js
CHANGED
|
@@ -275,7 +275,7 @@ var DocumentStore = class {
|
|
|
275
275
|
updated: now,
|
|
276
276
|
...cleaned
|
|
277
277
|
};
|
|
278
|
-
const fileName = type === "meeting" ? `${now.slice(0, 10)}-${slugify(fullFrontmatter.title)}.md` : `${id}.md`;
|
|
278
|
+
const fileName = type === "meeting" ? `${cleaned.date?.slice(0, 10) ?? now.slice(0, 10)}-${slugify(fullFrontmatter.title)}.md` : `${id}.md`;
|
|
279
279
|
const filePath = path3.join(dir, fileName);
|
|
280
280
|
const doc = {
|
|
281
281
|
frontmatter: fullFrontmatter,
|
|
@@ -299,7 +299,7 @@ var DocumentStore = class {
|
|
|
299
299
|
}
|
|
300
300
|
const dir = path3.join(this.docsDir, dirName);
|
|
301
301
|
fs3.mkdirSync(dir, { recursive: true });
|
|
302
|
-
const fileName = type === "meeting" ? `${frontmatter.created.slice(0, 10)}-${slugify(frontmatter.title)}.md` : `${frontmatter.id}.md`;
|
|
302
|
+
const fileName = type === "meeting" ? `${frontmatter.date?.slice(0, 10) ?? frontmatter.created.slice(0, 10)}-${slugify(frontmatter.title)}.md` : `${frontmatter.id}.md`;
|
|
303
303
|
const filePath = path3.join(dir, fileName);
|
|
304
304
|
const doc = { frontmatter, content, filePath };
|
|
305
305
|
fs3.writeFileSync(filePath, serializeDocument(doc), "utf-8");
|
|
@@ -15176,7 +15176,7 @@ function createMeetingTools(store) {
|
|
|
15176
15176
|
owner: external_exports.string().optional().describe("Meeting organizer"),
|
|
15177
15177
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
|
|
15178
15178
|
attendees: external_exports.array(external_exports.string()).optional().describe("List of attendees"),
|
|
15179
|
-
date: external_exports.string().
|
|
15179
|
+
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.")
|
|
15180
15180
|
},
|
|
15181
15181
|
async (args) => {
|
|
15182
15182
|
const frontmatter = {
|
|
@@ -15186,7 +15186,7 @@ function createMeetingTools(store) {
|
|
|
15186
15186
|
if (args.owner) frontmatter.owner = args.owner;
|
|
15187
15187
|
if (args.tags) frontmatter.tags = args.tags;
|
|
15188
15188
|
if (args.attendees) frontmatter.attendees = args.attendees;
|
|
15189
|
-
|
|
15189
|
+
frontmatter.date = args.date;
|
|
15190
15190
|
const doc = store.create(
|
|
15191
15191
|
"meeting",
|
|
15192
15192
|
frontmatter,
|
|
@@ -15886,7 +15886,7 @@ var genericAgilePlugin = {
|
|
|
15886
15886
|
|
|
15887
15887
|
**Meeting Tools:**
|
|
15888
15888
|
- **list_meetings** / **get_meeting**: Browse and read meeting records.
|
|
15889
|
-
- **create_meeting**: Record new meetings with attendees, date, and agenda.
|
|
15889
|
+
- **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.
|
|
15890
15890
|
- **update_meeting**: Update meeting status or notes after completion.
|
|
15891
15891
|
- **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
|
|
15892
15892
|
|
|
@@ -15907,7 +15907,7 @@ var genericAgilePlugin = {
|
|
|
15907
15907
|
|
|
15908
15908
|
**Meeting Tools:**
|
|
15909
15909
|
- **list_meetings** / **get_meeting**: Browse and read meeting records.
|
|
15910
|
-
- **create_meeting**: Record new meetings with attendees, date, and agenda.
|
|
15910
|
+
- **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.
|
|
15911
15911
|
- **update_meeting**: Update meeting status or notes after completion.
|
|
15912
15912
|
- **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
|
|
15913
15913
|
|
|
@@ -15935,7 +15935,7 @@ var genericAgilePlugin = {
|
|
|
15935
15935
|
|
|
15936
15936
|
**Meeting Tools:**
|
|
15937
15937
|
- **list_meetings** / **get_meeting**: Browse and read meeting records.
|
|
15938
|
-
- **create_meeting**: Record new meetings with attendees, date, and agenda.
|
|
15938
|
+
- **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.
|
|
15939
15939
|
- **update_meeting**: Update meeting status or notes after completion.
|
|
15940
15940
|
- **analyze_meeting**: Analyze a completed meeting to extract decisions, actions, and questions. Use this to ensure meeting outcomes are properly tracked as governance artifacts.
|
|
15941
15941
|
|
|
@@ -15953,7 +15953,7 @@ var genericAgilePlugin = {
|
|
|
15953
15953
|
**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.
|
|
15954
15954
|
|
|
15955
15955
|
- **list_meetings** / **get_meeting**: Browse and read meeting records.
|
|
15956
|
-
- **create_meeting**: Record meetings with attendees, date, and agenda.
|
|
15956
|
+
- **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.
|
|
15957
15957
|
- **update_meeting**: Update meeting status or notes.
|
|
15958
15958
|
- **analyze_meeting**: Analyze a meeting to extract decisions, actions, and questions as governance artifacts.`
|
|
15959
15959
|
}
|
|
@@ -17452,6 +17452,213 @@ function createSkillActionTools(skills, context) {
|
|
|
17452
17452
|
return tools;
|
|
17453
17453
|
}
|
|
17454
17454
|
|
|
17455
|
+
// src/mcp/persona-context.ts
|
|
17456
|
+
var PersonaContextManager = class {
|
|
17457
|
+
activePersona = null;
|
|
17458
|
+
setPersona(idOrShortName) {
|
|
17459
|
+
const persona = getPersona(idOrShortName);
|
|
17460
|
+
if (persona) {
|
|
17461
|
+
this.activePersona = persona;
|
|
17462
|
+
}
|
|
17463
|
+
return persona;
|
|
17464
|
+
}
|
|
17465
|
+
getActivePersona() {
|
|
17466
|
+
return this.activePersona;
|
|
17467
|
+
}
|
|
17468
|
+
clearPersona() {
|
|
17469
|
+
this.activePersona = null;
|
|
17470
|
+
}
|
|
17471
|
+
isDocumentTypeAllowed(docType) {
|
|
17472
|
+
if (!this.activePersona) return true;
|
|
17473
|
+
return this.activePersona.documentTypes.includes(docType);
|
|
17474
|
+
}
|
|
17475
|
+
};
|
|
17476
|
+
function buildMcpGuidance(persona, marvinDir) {
|
|
17477
|
+
const parts = [];
|
|
17478
|
+
parts.push(`# Active Persona: ${persona.name} (${persona.shortName})`);
|
|
17479
|
+
parts.push(`
|
|
17480
|
+
${persona.description}`);
|
|
17481
|
+
parts.push(`
|
|
17482
|
+
## Focus Areas`);
|
|
17483
|
+
for (const area of persona.focusAreas) {
|
|
17484
|
+
parts.push(`- ${area}`);
|
|
17485
|
+
}
|
|
17486
|
+
parts.push(`
|
|
17487
|
+
## Allowed Document Types`);
|
|
17488
|
+
parts.push(persona.documentTypes.join(", "));
|
|
17489
|
+
parts.push(`
|
|
17490
|
+
## Behavioral Instructions`);
|
|
17491
|
+
parts.push(persona.systemPrompt);
|
|
17492
|
+
try {
|
|
17493
|
+
const config2 = loadProjectConfig(marvinDir);
|
|
17494
|
+
const plugin = resolvePlugin(config2.methodology);
|
|
17495
|
+
if (plugin) {
|
|
17496
|
+
const fragment = getPluginPromptFragment(plugin, persona.id);
|
|
17497
|
+
if (fragment) {
|
|
17498
|
+
parts.push(`
|
|
17499
|
+
## Plugin Rules`);
|
|
17500
|
+
parts.push(fragment);
|
|
17501
|
+
}
|
|
17502
|
+
}
|
|
17503
|
+
const allSkills = loadAllSkills(marvinDir);
|
|
17504
|
+
const skillIds = resolveSkillsForPersona(
|
|
17505
|
+
persona.id,
|
|
17506
|
+
config2.skills,
|
|
17507
|
+
allSkills
|
|
17508
|
+
);
|
|
17509
|
+
if (skillIds.length > 0) {
|
|
17510
|
+
const fragment = getSkillPromptFragment(skillIds, allSkills, persona.id);
|
|
17511
|
+
if (fragment) {
|
|
17512
|
+
parts.push(`
|
|
17513
|
+
## Skill Rules`);
|
|
17514
|
+
parts.push(fragment);
|
|
17515
|
+
}
|
|
17516
|
+
}
|
|
17517
|
+
} catch {
|
|
17518
|
+
}
|
|
17519
|
+
return parts.join("\n");
|
|
17520
|
+
}
|
|
17521
|
+
function buildPersonaSummaries() {
|
|
17522
|
+
const personas = listPersonas();
|
|
17523
|
+
const lines = personas.map(
|
|
17524
|
+
(p) => `- **${p.name}** (${p.shortName}): ${p.description}
|
|
17525
|
+
Document types: ${p.documentTypes.join(", ")}`
|
|
17526
|
+
);
|
|
17527
|
+
return `# Available Personas
|
|
17528
|
+
|
|
17529
|
+
${lines.join("\n\n")}`;
|
|
17530
|
+
}
|
|
17531
|
+
|
|
17532
|
+
// src/mcp/persona-tools.ts
|
|
17533
|
+
import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
|
|
17534
|
+
function createPersonaTools(ctx, marvinDir) {
|
|
17535
|
+
return [
|
|
17536
|
+
tool17(
|
|
17537
|
+
"set_persona",
|
|
17538
|
+
"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.",
|
|
17539
|
+
{
|
|
17540
|
+
persona: external_exports.string().describe(
|
|
17541
|
+
'Persona ID or short name (e.g. "po", "product-owner", "dm", "delivery-manager", "tl", "tech-lead")'
|
|
17542
|
+
)
|
|
17543
|
+
},
|
|
17544
|
+
async (args) => {
|
|
17545
|
+
const resolved = ctx.setPersona(args.persona);
|
|
17546
|
+
if (!resolved) {
|
|
17547
|
+
const summaries = buildPersonaSummaries();
|
|
17548
|
+
return {
|
|
17549
|
+
content: [
|
|
17550
|
+
{
|
|
17551
|
+
type: "text",
|
|
17552
|
+
text: `Unknown persona "${args.persona}".
|
|
17553
|
+
|
|
17554
|
+
${summaries}`
|
|
17555
|
+
}
|
|
17556
|
+
],
|
|
17557
|
+
isError: true
|
|
17558
|
+
};
|
|
17559
|
+
}
|
|
17560
|
+
const guidance = buildMcpGuidance(resolved, marvinDir);
|
|
17561
|
+
return {
|
|
17562
|
+
content: [{ type: "text", text: guidance }]
|
|
17563
|
+
};
|
|
17564
|
+
}
|
|
17565
|
+
),
|
|
17566
|
+
tool17(
|
|
17567
|
+
"get_persona_guidance",
|
|
17568
|
+
"Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
|
|
17569
|
+
{
|
|
17570
|
+
persona: external_exports.string().optional().describe(
|
|
17571
|
+
"Optional persona ID or short name. Omit to list all personas."
|
|
17572
|
+
)
|
|
17573
|
+
},
|
|
17574
|
+
async (args) => {
|
|
17575
|
+
if (!args.persona) {
|
|
17576
|
+
const summaries = buildPersonaSummaries();
|
|
17577
|
+
return {
|
|
17578
|
+
content: [{ type: "text", text: summaries }]
|
|
17579
|
+
};
|
|
17580
|
+
}
|
|
17581
|
+
const resolved = getPersona(args.persona);
|
|
17582
|
+
if (!resolved) {
|
|
17583
|
+
const summaries = buildPersonaSummaries();
|
|
17584
|
+
return {
|
|
17585
|
+
content: [
|
|
17586
|
+
{
|
|
17587
|
+
type: "text",
|
|
17588
|
+
text: `Unknown persona "${args.persona}".
|
|
17589
|
+
|
|
17590
|
+
${summaries}`
|
|
17591
|
+
}
|
|
17592
|
+
],
|
|
17593
|
+
isError: true
|
|
17594
|
+
};
|
|
17595
|
+
}
|
|
17596
|
+
const guidance = buildMcpGuidance(resolved, marvinDir);
|
|
17597
|
+
return {
|
|
17598
|
+
content: [{ type: "text", text: guidance }]
|
|
17599
|
+
};
|
|
17600
|
+
},
|
|
17601
|
+
{ annotations: { readOnly: true } }
|
|
17602
|
+
)
|
|
17603
|
+
];
|
|
17604
|
+
}
|
|
17605
|
+
|
|
17606
|
+
// src/mcp/tool-wrapper.ts
|
|
17607
|
+
function extractDocType(toolName) {
|
|
17608
|
+
if (toolName === "save_report") return "report";
|
|
17609
|
+
const match = toolName.match(/^(?:create|update)_(\w+)$/);
|
|
17610
|
+
return match ? match[1] : void 0;
|
|
17611
|
+
}
|
|
17612
|
+
function wrapToolsWithPersonaValidation(tools, ctx) {
|
|
17613
|
+
return tools.map((t) => {
|
|
17614
|
+
const docType = extractDocType(t.name);
|
|
17615
|
+
if (!docType) return t;
|
|
17616
|
+
return {
|
|
17617
|
+
...t,
|
|
17618
|
+
handler: async (args, extra) => {
|
|
17619
|
+
const persona = ctx.getActivePersona();
|
|
17620
|
+
if (!persona) {
|
|
17621
|
+
const summaries = buildPersonaSummaries();
|
|
17622
|
+
return {
|
|
17623
|
+
content: [
|
|
17624
|
+
{
|
|
17625
|
+
type: "text",
|
|
17626
|
+
text: `[PERSONA REQUIRED] You must set an active persona before creating or updating documents. Call the set_persona tool first.
|
|
17627
|
+
|
|
17628
|
+
${summaries}`
|
|
17629
|
+
}
|
|
17630
|
+
],
|
|
17631
|
+
isError: true
|
|
17632
|
+
};
|
|
17633
|
+
}
|
|
17634
|
+
const result = await t.handler(args, extra);
|
|
17635
|
+
if (ctx.isDocumentTypeAllowed(docType)) {
|
|
17636
|
+
return result;
|
|
17637
|
+
}
|
|
17638
|
+
const warning = [
|
|
17639
|
+
`[PERSONA WARNING] You are acting as ${persona.name} (${persona.shortName}). Creating/updating "${docType}" documents is outside your typical scope.`,
|
|
17640
|
+
"",
|
|
17641
|
+
`Your allowed document types: ${persona.documentTypes.join(", ")}`,
|
|
17642
|
+
"",
|
|
17643
|
+
"Consider whether this is the right persona for this task, or switch with set_persona.",
|
|
17644
|
+
"",
|
|
17645
|
+
"---"
|
|
17646
|
+
].join("\n");
|
|
17647
|
+
const content = Array.isArray(result.content) ? result.content.map(
|
|
17648
|
+
(block, index) => {
|
|
17649
|
+
if (index === 0 && block.type === "text" && block.text) {
|
|
17650
|
+
return { ...block, text: `${warning}
|
|
17651
|
+
${block.text}` };
|
|
17652
|
+
}
|
|
17653
|
+
return block;
|
|
17654
|
+
}
|
|
17655
|
+
) : result.content;
|
|
17656
|
+
return { ...result, content };
|
|
17657
|
+
}
|
|
17658
|
+
};
|
|
17659
|
+
});
|
|
17660
|
+
}
|
|
17661
|
+
|
|
17455
17662
|
// src/mcp/stdio-server.ts
|
|
17456
17663
|
function collectTools(marvinDir) {
|
|
17457
17664
|
const config2 = loadProjectConfig(marvinDir);
|
|
@@ -17514,8 +17721,11 @@ async function startStdioServer(options) {
|
|
|
17514
17721
|
{ name: "marvin-governance", version: "0.1.0" },
|
|
17515
17722
|
{ capabilities: { tools: {} } }
|
|
17516
17723
|
);
|
|
17517
|
-
const
|
|
17518
|
-
|
|
17724
|
+
const contextManager = new PersonaContextManager();
|
|
17725
|
+
const governanceTools = collectTools(options.marvinDir);
|
|
17726
|
+
const personaTools = createPersonaTools(contextManager, options.marvinDir);
|
|
17727
|
+
const wrappedTools = wrapToolsWithPersonaValidation(governanceTools, contextManager);
|
|
17728
|
+
registerSdkTools(server, [...personaTools, ...wrappedTools]);
|
|
17519
17729
|
const transport = new StdioServerTransport();
|
|
17520
17730
|
await server.connect(transport);
|
|
17521
17731
|
}
|
|
@@ -19482,7 +19692,7 @@ function createProgram() {
|
|
|
19482
19692
|
const program = new Command();
|
|
19483
19693
|
program.name("marvin").description(
|
|
19484
19694
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
19485
|
-
).version("0.2.
|
|
19695
|
+
).version("0.2.1");
|
|
19486
19696
|
program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
19487
19697
|
await initCommand();
|
|
19488
19698
|
});
|