mrvn-cli 0.1.0 → 0.1.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/marvin.js CHANGED
@@ -14029,6 +14029,72 @@ function createMeetingTools(store) {
14029
14029
  ]
14030
14030
  };
14031
14031
  }
14032
+ ),
14033
+ tool(
14034
+ "analyze_meeting",
14035
+ "Analyze a meeting to identify decisions, actions, and questions. Returns the meeting content with existing project context so you can extract and create artifacts using the create_decision, create_action, and create_question tools.",
14036
+ {
14037
+ id: external_exports.string().describe("Meeting ID to analyze (e.g. 'M-001')"),
14038
+ include_context: external_exports.boolean().optional().describe("Include existing artifacts for dedup awareness (default: true)")
14039
+ },
14040
+ async (args) => {
14041
+ const doc = store.get(args.id);
14042
+ if (!doc) {
14043
+ return {
14044
+ content: [{ type: "text", text: `Meeting ${args.id} not found` }],
14045
+ isError: true
14046
+ };
14047
+ }
14048
+ if (doc.frontmatter.type !== "meeting") {
14049
+ return {
14050
+ content: [{ type: "text", text: `Document ${args.id} is not a meeting (type: ${doc.frontmatter.type})` }],
14051
+ isError: true
14052
+ };
14053
+ }
14054
+ const includeContext = args.include_context !== false;
14055
+ const sections = [];
14056
+ sections.push(`# Meeting: ${doc.frontmatter.title} (${args.id})`);
14057
+ sections.push(`**Status:** ${doc.frontmatter.status}`);
14058
+ sections.push(`**Date:** ${doc.frontmatter.date ?? doc.frontmatter.created}`);
14059
+ if (doc.frontmatter.attendees) {
14060
+ sections.push(`**Attendees:** ${doc.frontmatter.attendees.join(", ")}`);
14061
+ }
14062
+ sections.push("");
14063
+ sections.push(doc.content);
14064
+ if (includeContext) {
14065
+ const decisions = store.list({ type: "decision" });
14066
+ const actions = store.list({ type: "action" });
14067
+ const questions = store.list({ type: "question" });
14068
+ sections.push("\n---\n# Existing Artifacts (for dedup awareness)");
14069
+ if (decisions.length > 0) {
14070
+ sections.push("\n## Existing Decisions");
14071
+ for (const d of decisions) {
14072
+ sections.push(`- ${d.frontmatter.id}: ${d.frontmatter.title} [${d.frontmatter.status}]`);
14073
+ }
14074
+ }
14075
+ if (actions.length > 0) {
14076
+ sections.push("\n## Existing Actions");
14077
+ for (const a of actions) {
14078
+ sections.push(`- ${a.frontmatter.id}: ${a.frontmatter.title} [${a.frontmatter.status}]`);
14079
+ }
14080
+ }
14081
+ if (questions.length > 0) {
14082
+ sections.push("\n## Existing Questions");
14083
+ for (const q of questions) {
14084
+ sections.push(`- ${q.frontmatter.id}: ${q.frontmatter.title} [${q.frontmatter.status}]`);
14085
+ }
14086
+ }
14087
+ }
14088
+ sections.push("\n---\n# Instructions");
14089
+ sections.push(`Analyze this meeting and create artifacts using create_decision, create_action, and create_question tools.`);
14090
+ sections.push(`For each artifact, include the tag "source:${args.id}" for traceability.`);
14091
+ sections.push(`After creating artifacts, update the meeting using update_meeting to append an Outcomes section listing the created artifact IDs.`);
14092
+ sections.push(`Avoid creating duplicates of existing artifacts listed above.`);
14093
+ return {
14094
+ content: [{ type: "text", text: sections.join("\n") }]
14095
+ };
14096
+ },
14097
+ { annotations: { readOnly: true } }
14032
14098
  )
14033
14099
  ];
14034
14100
  }
@@ -14627,6 +14693,7 @@ var genericAgilePlugin = {
14627
14693
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
14628
14694
  - **create_meeting**: Record new meetings with attendees, date, and agenda.
14629
14695
  - **update_meeting**: Update meeting status or notes after completion.
14696
+ - **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
14630
14697
 
14631
14698
  **Key Workflow Rules:**
14632
14699
  - Create features as "draft" and approve them when requirements are clear and prioritized.
@@ -14647,6 +14714,7 @@ var genericAgilePlugin = {
14647
14714
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
14648
14715
  - **create_meeting**: Record new meetings with attendees, date, and agenda.
14649
14716
  - **update_meeting**: Update meeting status or notes after completion.
14717
+ - **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
14650
14718
 
14651
14719
  **Key Workflow Rules:**
14652
14720
  - Only create epics against approved features \u2014 create_epic enforces this.
@@ -14674,11 +14742,13 @@ var genericAgilePlugin = {
14674
14742
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
14675
14743
  - **create_meeting**: Record new meetings with attendees, date, and agenda.
14676
14744
  - **update_meeting**: Update meeting status or notes after completion.
14745
+ - **analyze_meeting**: Analyze a completed meeting to extract decisions, actions, and questions. Use this to ensure meeting outcomes are properly tracked as governance artifacts.
14677
14746
 
14678
14747
  **Key Workflow Rules:**
14679
14748
  - After generating any report, offer to save it with save_report for audit trail.
14680
14749
  - Proactively flag risks: unowned actions, overdue items, epics linked to deferred features.
14681
- - Use feature progress reports for stakeholder updates and epic progress for sprint-level tracking.`,
14750
+ - Use feature progress reports for stakeholder updates and epic progress for sprint-level tracking.
14751
+ - Use analyze_meeting after meetings to extract outcomes into governance artifacts.`,
14682
14752
  "*": `You have access to feature, epic, and meeting tools for project coordination:
14683
14753
 
14684
14754
  **Features** (F-xxx): Product capabilities defined by the Product Owner. Features progress through draft \u2192 approved \u2192 done.
@@ -14689,7 +14759,8 @@ var genericAgilePlugin = {
14689
14759
 
14690
14760
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
14691
14761
  - **create_meeting**: Record meetings with attendees, date, and agenda.
14692
- - **update_meeting**: Update meeting status or notes.`
14762
+ - **update_meeting**: Update meeting status or notes.
14763
+ - **analyze_meeting**: Analyze a meeting to extract decisions, actions, and questions as governance artifacts.`
14693
14764
  }
14694
14765
  };
14695
14766
 
@@ -15975,9 +16046,9 @@ var DocumentStore = class {
15975
16046
  }
15976
16047
  }
15977
16048
  }
15978
- list(query5) {
16049
+ list(query6) {
15979
16050
  const results = [];
15980
- const types = query5?.type ? [query5.type] : Object.keys(this.typeDirs);
16051
+ const types = query6?.type ? [query6.type] : Object.keys(this.typeDirs);
15981
16052
  for (const type of types) {
15982
16053
  const dirName = this.typeDirs[type];
15983
16054
  if (!dirName) continue;
@@ -15988,9 +16059,9 @@ var DocumentStore = class {
15988
16059
  const filePath = path5.join(dir, file2);
15989
16060
  const raw = fs5.readFileSync(filePath, "utf-8");
15990
16061
  const doc = parseDocument(raw, filePath);
15991
- if (query5?.status && doc.frontmatter.status !== query5.status) continue;
15992
- if (query5?.owner && doc.frontmatter.owner !== query5.owner) continue;
15993
- if (query5?.tag && (!doc.frontmatter.tags || !doc.frontmatter.tags.includes(query5.tag)))
16062
+ if (query6?.status && doc.frontmatter.status !== query6.status) continue;
16063
+ if (query6?.owner && doc.frontmatter.owner !== query6.owner) continue;
16064
+ if (query6?.tag && (!doc.frontmatter.tags || !doc.frontmatter.tags.includes(query6.tag)))
15994
16065
  continue;
15995
16066
  results.push(doc);
15996
16067
  }
@@ -17232,6 +17303,7 @@ Marvin \u2014 ${persona.name}
17232
17303
  "mcp__marvin-governance__get_source_info",
17233
17304
  "mcp__marvin-governance__list_sessions",
17234
17305
  "mcp__marvin-governance__get_session",
17306
+ "mcp__marvin-governance__analyze_meeting",
17235
17307
  ...pluginTools.map((t) => `mcp__marvin-governance__${t.name}`),
17236
17308
  ...codeSkillTools.map((t) => `mcp__marvin-governance__${t.name}`),
17237
17309
  ...actionTools.map((t) => `mcp__marvin-governance__${t.name}`)
@@ -18922,6 +18994,263 @@ async function importCommand(inputPath, options) {
18922
18994
  }
18923
18995
  }
18924
18996
 
18997
+ // src/cli/commands/analyze.ts
18998
+ import chalk13 from "chalk";
18999
+
19000
+ // src/analysis/analyze.ts
19001
+ import chalk12 from "chalk";
19002
+ import ora4 from "ora";
19003
+ import { query as query5 } from "@anthropic-ai/claude-agent-sdk";
19004
+
19005
+ // src/analysis/prompts.ts
19006
+ function buildAnalyzeSystemPrompt(persona, projectConfig, isDraft) {
19007
+ const parts = [];
19008
+ parts.push(persona.systemPrompt);
19009
+ parts.push(`
19010
+ ## Project Context
19011
+ - **Project Name:** ${projectConfig.name}
19012
+ - **Methodology:** ${projectConfig.methodology ?? "Generic Agile"}
19013
+ `);
19014
+ parts.push(`
19015
+ ## Meeting Analysis Task
19016
+ You are analyzing a meeting record to extract governance artifacts for the project.
19017
+ Review the meeting content thoroughly and identify:
19018
+
19019
+ 1. **Decisions** (D-xxx): Any decisions made during the meeting \u2014 architectural choices, process agreements, technology selections, or resolved discussion points.
19020
+ 2. **Actions** (A-xxx): Tasks assigned, follow-ups agreed upon, work items to be done, or commitments made during the meeting.
19021
+ 3. **Questions** (Q-xxx): Open questions raised, unresolved discussion points, items needing further investigation, or clarifications needed.
19022
+
19023
+ For each artifact, provide:
19024
+ - A clear, concise title
19025
+ - Detailed content with rationale or context from the meeting discussion
19026
+ - Appropriate status (decisions: "decided" or "open"; actions: "open"; questions: "open")
19027
+ - Relevant tags for categorization
19028
+ `);
19029
+ if (isDraft) {
19030
+ parts.push(`
19031
+ ## Mode: Draft Proposal
19032
+ Present your findings as a structured proposal. Do NOT create any artifacts.
19033
+ Format your response as:
19034
+
19035
+ ### Proposed Decisions
19036
+ For each decision:
19037
+ - **Title:** [title]
19038
+ - **Status:** [decided/open]
19039
+ - **Content:** [description and rationale from the meeting]
19040
+ - **Tags:** [comma-separated tags]
19041
+
19042
+ ### Proposed Actions
19043
+ For each action:
19044
+ - **Title:** [title]
19045
+ - **Assignee:** [who was assigned, if mentioned]
19046
+ - **Content:** [what needs to be done]
19047
+ - **Tags:** [comma-separated tags]
19048
+
19049
+ ### Proposed Questions
19050
+ For each question:
19051
+ - **Title:** [title]
19052
+ - **Content:** [what needs clarification and why]
19053
+ - **Tags:** [comma-separated tags]
19054
+
19055
+ ### Summary
19056
+ Provide a brief summary of the meeting outcomes and any recommendations.
19057
+ `);
19058
+ } else {
19059
+ parts.push(`
19060
+ ## Mode: Direct Creation
19061
+ Use the MCP tools to create artifacts directly:
19062
+ - Use \`create_decision\` for decisions
19063
+ - Use \`create_action\` for action items
19064
+ - Use \`create_question\` for questions
19065
+
19066
+ Before creating artifacts, check existing ones using the list tools to avoid duplicates.
19067
+
19068
+ For EVERY artifact you create, include:
19069
+ - A \`source:<meeting-id>\` tag in the tags array (the meeting ID is provided in the user prompt)
19070
+ - Clear title and detailed content with context from the meeting
19071
+
19072
+ After creating all artifacts, provide a summary of what was created.
19073
+ `);
19074
+ }
19075
+ return parts.join("\n");
19076
+ }
19077
+ function buildAnalyzeUserPrompt(meetingId, meetingContent, meetingTitle, isDraft) {
19078
+ const mode = isDraft ? "propose" : "create";
19079
+ return `Please analyze the following meeting record and ${mode} governance artifacts (decisions, actions, questions) based on its content.
19080
+
19081
+ **Meeting ID:** ${meetingId}
19082
+ **Meeting Title:** ${meetingTitle}
19083
+
19084
+ ---
19085
+ ${meetingContent}
19086
+ ---
19087
+
19088
+ Analyze this meeting thoroughly and ${mode} all relevant governance artifacts. Tag each artifact with "source:${meetingId}" for traceability back to this meeting.`;
19089
+ }
19090
+
19091
+ // src/analysis/analyze.ts
19092
+ async function analyzeMeeting(options) {
19093
+ const { marvinDir, meetingId, draft, persona: personaInput } = options;
19094
+ const config2 = getConfig(marvinDir);
19095
+ const personaId = resolvePersonaId(personaInput);
19096
+ const persona = getPersona(personaId);
19097
+ const plugin = resolvePlugin(config2.project.methodology);
19098
+ const registrations = plugin?.documentTypeRegistrations ?? [];
19099
+ const store = new DocumentStore(marvinDir, registrations);
19100
+ const meetingDoc = store.get(meetingId);
19101
+ if (!meetingDoc) {
19102
+ throw new Error(`Meeting ${meetingId} not found`);
19103
+ }
19104
+ if (meetingDoc.frontmatter.type !== "meeting") {
19105
+ throw new Error(`Document ${meetingId} is not a meeting (type: ${meetingDoc.frontmatter.type})`);
19106
+ }
19107
+ const createdArtifacts = [];
19108
+ if (!draft) {
19109
+ const originalCreate = store.create.bind(store);
19110
+ store.create = (type, frontmatter, content) => {
19111
+ const tags = frontmatter.tags ?? [];
19112
+ const sourceTag = `source:${meetingId}`;
19113
+ if (!tags.includes(sourceTag)) {
19114
+ tags.push(sourceTag);
19115
+ }
19116
+ const doc = originalCreate(type, { ...frontmatter, source: meetingId, tags }, content);
19117
+ createdArtifacts.push(doc.frontmatter.id);
19118
+ return doc;
19119
+ };
19120
+ }
19121
+ const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
19122
+ const mcpServer = createMarvinMcpServer(store, { pluginTools });
19123
+ const systemPrompt = buildAnalyzeSystemPrompt(persona, config2.project, draft);
19124
+ const userPrompt = buildAnalyzeUserPrompt(
19125
+ meetingId,
19126
+ meetingDoc.content,
19127
+ meetingDoc.frontmatter.title,
19128
+ draft
19129
+ );
19130
+ const spinner = ora4({ text: `Analyzing meeting ${meetingId}...`, color: "cyan" });
19131
+ spinner.start();
19132
+ try {
19133
+ const conversation = query5({
19134
+ prompt: userPrompt,
19135
+ options: {
19136
+ systemPrompt,
19137
+ mcpServers: { "marvin-governance": mcpServer },
19138
+ permissionMode: "acceptEdits",
19139
+ maxTurns: 10,
19140
+ tools: [],
19141
+ allowedTools: [
19142
+ "mcp__marvin-governance__create_decision",
19143
+ "mcp__marvin-governance__create_action",
19144
+ "mcp__marvin-governance__create_question",
19145
+ "mcp__marvin-governance__list_decisions",
19146
+ "mcp__marvin-governance__list_actions",
19147
+ "mcp__marvin-governance__list_questions"
19148
+ ]
19149
+ }
19150
+ });
19151
+ for await (const message of conversation) {
19152
+ handleAnalyzeMessage(message, spinner);
19153
+ }
19154
+ if (!draft && createdArtifacts.length > 0) {
19155
+ appendOutcomesToMeeting(store, meetingDoc.frontmatter.id, meetingDoc.content, createdArtifacts, store);
19156
+ }
19157
+ spinner.stop();
19158
+ if (draft) {
19159
+ console.log(chalk12.dim(`
19160
+ Draft proposal complete. No artifacts were created.`));
19161
+ console.log(chalk12.dim(`Use "marvin analyze ${meetingId} --no-draft" to create artifacts.`));
19162
+ } else {
19163
+ console.log(chalk12.green(`
19164
+ Created ${createdArtifacts.length} artifact${createdArtifacts.length === 1 ? "" : "s"} from meeting ${meetingId}`));
19165
+ if (createdArtifacts.length > 0) {
19166
+ console.log(chalk12.dim(` ${createdArtifacts.join(", ")}`));
19167
+ }
19168
+ }
19169
+ return { meetingId, artifacts: createdArtifacts, draft };
19170
+ } catch (err) {
19171
+ spinner.stop();
19172
+ throw err;
19173
+ }
19174
+ }
19175
+ function appendOutcomesToMeeting(store, meetingId, existingContent, artifacts, storeInstance) {
19176
+ const outcomeLines = artifacts.map((id) => {
19177
+ const doc = storeInstance.get(id);
19178
+ const title = doc ? doc.frontmatter.title : id;
19179
+ return `- ${id}: ${title}`;
19180
+ });
19181
+ const outcomesSection = `
19182
+
19183
+ ## Outcomes
19184
+ ${outcomeLines.join("\n")}`;
19185
+ const updatedContent = existingContent + outcomesSection;
19186
+ store.update(meetingId, {}, updatedContent);
19187
+ }
19188
+ function handleAnalyzeMessage(message, spinner) {
19189
+ switch (message.type) {
19190
+ case "assistant": {
19191
+ spinner.stop();
19192
+ const textBlocks = message.message.content.filter(
19193
+ (b) => b.type === "text"
19194
+ );
19195
+ if (textBlocks.length > 0) {
19196
+ console.log(
19197
+ chalk12.cyan("\nMarvin: ") + textBlocks.map((b) => b.text).join("\n")
19198
+ );
19199
+ }
19200
+ break;
19201
+ }
19202
+ case "system": {
19203
+ if (message.subtype === "init") {
19204
+ spinner.start("Analyzing...");
19205
+ }
19206
+ break;
19207
+ }
19208
+ case "result": {
19209
+ spinner.stop();
19210
+ if (message.subtype !== "success") {
19211
+ console.log(
19212
+ chalk12.red(`
19213
+ Analysis ended with error: ${message.subtype}`)
19214
+ );
19215
+ }
19216
+ break;
19217
+ }
19218
+ }
19219
+ }
19220
+
19221
+ // src/cli/commands/analyze.ts
19222
+ async function analyzeCommand(meetingId, options) {
19223
+ const project = loadProject();
19224
+ const marvinDir = project.marvinDir;
19225
+ const config2 = getConfig(marvinDir);
19226
+ const plugin = resolvePlugin(config2.project.methodology);
19227
+ const registrations = plugin?.documentTypeRegistrations ?? [];
19228
+ const store = new DocumentStore(marvinDir, registrations);
19229
+ const meetingDoc = store.get(meetingId);
19230
+ if (!meetingDoc) {
19231
+ console.log(chalk13.red(`Meeting ${meetingId} not found.`));
19232
+ console.log(chalk13.dim(`Use "marvin chat --as dm" to create meetings, or check the ID.`));
19233
+ return;
19234
+ }
19235
+ if (meetingDoc.frontmatter.type !== "meeting") {
19236
+ console.log(chalk13.red(`Document ${meetingId} is not a meeting (type: ${meetingDoc.frontmatter.type}).`));
19237
+ return;
19238
+ }
19239
+ const isDraft = options.draft !== false;
19240
+ const persona = options.as ?? "delivery-manager";
19241
+ console.log(chalk13.bold(`
19242
+ Analyzing meeting: ${meetingDoc.frontmatter.title}`));
19243
+ console.log(chalk13.dim(`Mode: ${isDraft ? "draft (propose only)" : "direct (create artifacts)"}`));
19244
+ console.log(chalk13.dim(`Persona: ${persona}
19245
+ `));
19246
+ await analyzeMeeting({
19247
+ marvinDir,
19248
+ meetingId,
19249
+ draft: isDraft,
19250
+ persona
19251
+ });
19252
+ }
19253
+
18925
19254
  // src/cli/program.ts
18926
19255
  function createProgram() {
18927
19256
  const program2 = new Command();
@@ -18967,6 +19296,9 @@ function createProgram() {
18967
19296
  program2.command("clone <url> [directory]").description("Clone governance data from a remote repository").action(async (url2, directory) => {
18968
19297
  await cloneCommand(url2, directory);
18969
19298
  });
19299
+ program2.command("analyze <meeting-id>").description("Analyze a meeting to extract decisions, actions, and questions").option("--draft", "Propose artifacts without creating them (default)").option("--no-draft", "Create artifacts directly via MCP tools").option("--as <persona>", "Persona for analysis (default: delivery-manager)").action(async (meetingId, options) => {
19300
+ await analyzeCommand(meetingId, options);
19301
+ });
18970
19302
  program2.command("import <path>").description("Import documents or sources from external paths").option("--dry-run", "Preview without writing files").option(
18971
19303
  "--conflict <strategy>",
18972
19304
  "ID conflict strategy: renumber, skip, overwrite (default: renumber)"