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/index.js CHANGED
@@ -218,9 +218,9 @@ var DocumentStore = class {
218
218
  }
219
219
  }
220
220
  }
221
- list(query5) {
221
+ list(query6) {
222
222
  const results = [];
223
- const types = query5?.type ? [query5.type] : Object.keys(this.typeDirs);
223
+ const types = query6?.type ? [query6.type] : Object.keys(this.typeDirs);
224
224
  for (const type of types) {
225
225
  const dirName = this.typeDirs[type];
226
226
  if (!dirName) continue;
@@ -231,9 +231,9 @@ var DocumentStore = class {
231
231
  const filePath = path3.join(dir, file2);
232
232
  const raw = fs3.readFileSync(filePath, "utf-8");
233
233
  const doc = parseDocument(raw, filePath);
234
- if (query5?.status && doc.frontmatter.status !== query5.status) continue;
235
- if (query5?.owner && doc.frontmatter.owner !== query5.owner) continue;
236
- if (query5?.tag && (!doc.frontmatter.tags || !doc.frontmatter.tags.includes(query5.tag)))
234
+ if (query6?.status && doc.frontmatter.status !== query6.status) continue;
235
+ if (query6?.owner && doc.frontmatter.owner !== query6.owner) continue;
236
+ if (query6?.tag && (!doc.frontmatter.tags || !doc.frontmatter.tags.includes(query6.tag)))
237
237
  continue;
238
238
  results.push(doc);
239
239
  }
@@ -15224,6 +15224,72 @@ function createMeetingTools(store) {
15224
15224
  ]
15225
15225
  };
15226
15226
  }
15227
+ ),
15228
+ tool7(
15229
+ "analyze_meeting",
15230
+ "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.",
15231
+ {
15232
+ id: external_exports.string().describe("Meeting ID to analyze (e.g. 'M-001')"),
15233
+ include_context: external_exports.boolean().optional().describe("Include existing artifacts for dedup awareness (default: true)")
15234
+ },
15235
+ async (args) => {
15236
+ const doc = store.get(args.id);
15237
+ if (!doc) {
15238
+ return {
15239
+ content: [{ type: "text", text: `Meeting ${args.id} not found` }],
15240
+ isError: true
15241
+ };
15242
+ }
15243
+ if (doc.frontmatter.type !== "meeting") {
15244
+ return {
15245
+ content: [{ type: "text", text: `Document ${args.id} is not a meeting (type: ${doc.frontmatter.type})` }],
15246
+ isError: true
15247
+ };
15248
+ }
15249
+ const includeContext = args.include_context !== false;
15250
+ const sections = [];
15251
+ sections.push(`# Meeting: ${doc.frontmatter.title} (${args.id})`);
15252
+ sections.push(`**Status:** ${doc.frontmatter.status}`);
15253
+ sections.push(`**Date:** ${doc.frontmatter.date ?? doc.frontmatter.created}`);
15254
+ if (doc.frontmatter.attendees) {
15255
+ sections.push(`**Attendees:** ${doc.frontmatter.attendees.join(", ")}`);
15256
+ }
15257
+ sections.push("");
15258
+ sections.push(doc.content);
15259
+ if (includeContext) {
15260
+ const decisions = store.list({ type: "decision" });
15261
+ const actions = store.list({ type: "action" });
15262
+ const questions = store.list({ type: "question" });
15263
+ sections.push("\n---\n# Existing Artifacts (for dedup awareness)");
15264
+ if (decisions.length > 0) {
15265
+ sections.push("\n## Existing Decisions");
15266
+ for (const d of decisions) {
15267
+ sections.push(`- ${d.frontmatter.id}: ${d.frontmatter.title} [${d.frontmatter.status}]`);
15268
+ }
15269
+ }
15270
+ if (actions.length > 0) {
15271
+ sections.push("\n## Existing Actions");
15272
+ for (const a of actions) {
15273
+ sections.push(`- ${a.frontmatter.id}: ${a.frontmatter.title} [${a.frontmatter.status}]`);
15274
+ }
15275
+ }
15276
+ if (questions.length > 0) {
15277
+ sections.push("\n## Existing Questions");
15278
+ for (const q of questions) {
15279
+ sections.push(`- ${q.frontmatter.id}: ${q.frontmatter.title} [${q.frontmatter.status}]`);
15280
+ }
15281
+ }
15282
+ }
15283
+ sections.push("\n---\n# Instructions");
15284
+ sections.push(`Analyze this meeting and create artifacts using create_decision, create_action, and create_question tools.`);
15285
+ sections.push(`For each artifact, include the tag "source:${args.id}" for traceability.`);
15286
+ sections.push(`After creating artifacts, update the meeting using update_meeting to append an Outcomes section listing the created artifact IDs.`);
15287
+ sections.push(`Avoid creating duplicates of existing artifacts listed above.`);
15288
+ return {
15289
+ content: [{ type: "text", text: sections.join("\n") }]
15290
+ };
15291
+ },
15292
+ { annotations: { readOnly: true } }
15227
15293
  )
15228
15294
  ];
15229
15295
  }
@@ -15822,6 +15888,7 @@ var genericAgilePlugin = {
15822
15888
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15823
15889
  - **create_meeting**: Record new meetings with attendees, date, and agenda.
15824
15890
  - **update_meeting**: Update meeting status or notes after completion.
15891
+ - **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
15825
15892
 
15826
15893
  **Key Workflow Rules:**
15827
15894
  - Create features as "draft" and approve them when requirements are clear and prioritized.
@@ -15842,6 +15909,7 @@ var genericAgilePlugin = {
15842
15909
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15843
15910
  - **create_meeting**: Record new meetings with attendees, date, and agenda.
15844
15911
  - **update_meeting**: Update meeting status or notes after completion.
15912
+ - **analyze_meeting**: Analyze a meeting to review its outcomes and extract artifacts.
15845
15913
 
15846
15914
  **Key Workflow Rules:**
15847
15915
  - Only create epics against approved features \u2014 create_epic enforces this.
@@ -15869,11 +15937,13 @@ var genericAgilePlugin = {
15869
15937
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15870
15938
  - **create_meeting**: Record new meetings with attendees, date, and agenda.
15871
15939
  - **update_meeting**: Update meeting status or notes after completion.
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.
15872
15941
 
15873
15942
  **Key Workflow Rules:**
15874
15943
  - After generating any report, offer to save it with save_report for audit trail.
15875
15944
  - Proactively flag risks: unowned actions, overdue items, epics linked to deferred features.
15876
- - Use feature progress reports for stakeholder updates and epic progress for sprint-level tracking.`,
15945
+ - Use feature progress reports for stakeholder updates and epic progress for sprint-level tracking.
15946
+ - Use analyze_meeting after meetings to extract outcomes into governance artifacts.`,
15877
15947
  "*": `You have access to feature, epic, and meeting tools for project coordination:
15878
15948
 
15879
15949
  **Features** (F-xxx): Product capabilities defined by the Product Owner. Features progress through draft \u2192 approved \u2192 done.
@@ -15884,7 +15954,8 @@ var genericAgilePlugin = {
15884
15954
 
15885
15955
  - **list_meetings** / **get_meeting**: Browse and read meeting records.
15886
15956
  - **create_meeting**: Record meetings with attendees, date, and agenda.
15887
- - **update_meeting**: Update meeting status or notes.`
15957
+ - **update_meeting**: Update meeting status or notes.
15958
+ - **analyze_meeting**: Analyze a meeting to extract decisions, actions, and questions as governance artifacts.`
15888
15959
  }
15889
15960
  };
15890
15961
 
@@ -17119,6 +17190,7 @@ Marvin \u2014 ${persona.name}
17119
17190
  "mcp__marvin-governance__get_source_info",
17120
17191
  "mcp__marvin-governance__list_sessions",
17121
17192
  "mcp__marvin-governance__get_session",
17193
+ "mcp__marvin-governance__analyze_meeting",
17122
17194
  ...pluginTools.map((t) => `mcp__marvin-governance__${t.name}`),
17123
17195
  ...codeSkillTools.map((t) => `mcp__marvin-governance__${t.name}`),
17124
17196
  ...actionTools.map((t) => `mcp__marvin-governance__${t.name}`)
@@ -18928,6 +19000,263 @@ async function importCommand(inputPath, options) {
18928
19000
  }
18929
19001
  }
18930
19002
 
19003
+ // src/cli/commands/analyze.ts
19004
+ import chalk13 from "chalk";
19005
+
19006
+ // src/analysis/analyze.ts
19007
+ import chalk12 from "chalk";
19008
+ import ora4 from "ora";
19009
+ import { query as query5 } from "@anthropic-ai/claude-agent-sdk";
19010
+
19011
+ // src/analysis/prompts.ts
19012
+ function buildAnalyzeSystemPrompt(persona, projectConfig, isDraft) {
19013
+ const parts = [];
19014
+ parts.push(persona.systemPrompt);
19015
+ parts.push(`
19016
+ ## Project Context
19017
+ - **Project Name:** ${projectConfig.name}
19018
+ - **Methodology:** ${projectConfig.methodology ?? "Generic Agile"}
19019
+ `);
19020
+ parts.push(`
19021
+ ## Meeting Analysis Task
19022
+ You are analyzing a meeting record to extract governance artifacts for the project.
19023
+ Review the meeting content thoroughly and identify:
19024
+
19025
+ 1. **Decisions** (D-xxx): Any decisions made during the meeting \u2014 architectural choices, process agreements, technology selections, or resolved discussion points.
19026
+ 2. **Actions** (A-xxx): Tasks assigned, follow-ups agreed upon, work items to be done, or commitments made during the meeting.
19027
+ 3. **Questions** (Q-xxx): Open questions raised, unresolved discussion points, items needing further investigation, or clarifications needed.
19028
+
19029
+ For each artifact, provide:
19030
+ - A clear, concise title
19031
+ - Detailed content with rationale or context from the meeting discussion
19032
+ - Appropriate status (decisions: "decided" or "open"; actions: "open"; questions: "open")
19033
+ - Relevant tags for categorization
19034
+ `);
19035
+ if (isDraft) {
19036
+ parts.push(`
19037
+ ## Mode: Draft Proposal
19038
+ Present your findings as a structured proposal. Do NOT create any artifacts.
19039
+ Format your response as:
19040
+
19041
+ ### Proposed Decisions
19042
+ For each decision:
19043
+ - **Title:** [title]
19044
+ - **Status:** [decided/open]
19045
+ - **Content:** [description and rationale from the meeting]
19046
+ - **Tags:** [comma-separated tags]
19047
+
19048
+ ### Proposed Actions
19049
+ For each action:
19050
+ - **Title:** [title]
19051
+ - **Assignee:** [who was assigned, if mentioned]
19052
+ - **Content:** [what needs to be done]
19053
+ - **Tags:** [comma-separated tags]
19054
+
19055
+ ### Proposed Questions
19056
+ For each question:
19057
+ - **Title:** [title]
19058
+ - **Content:** [what needs clarification and why]
19059
+ - **Tags:** [comma-separated tags]
19060
+
19061
+ ### Summary
19062
+ Provide a brief summary of the meeting outcomes and any recommendations.
19063
+ `);
19064
+ } else {
19065
+ parts.push(`
19066
+ ## Mode: Direct Creation
19067
+ Use the MCP tools to create artifacts directly:
19068
+ - Use \`create_decision\` for decisions
19069
+ - Use \`create_action\` for action items
19070
+ - Use \`create_question\` for questions
19071
+
19072
+ Before creating artifacts, check existing ones using the list tools to avoid duplicates.
19073
+
19074
+ For EVERY artifact you create, include:
19075
+ - A \`source:<meeting-id>\` tag in the tags array (the meeting ID is provided in the user prompt)
19076
+ - Clear title and detailed content with context from the meeting
19077
+
19078
+ After creating all artifacts, provide a summary of what was created.
19079
+ `);
19080
+ }
19081
+ return parts.join("\n");
19082
+ }
19083
+ function buildAnalyzeUserPrompt(meetingId, meetingContent, meetingTitle, isDraft) {
19084
+ const mode = isDraft ? "propose" : "create";
19085
+ return `Please analyze the following meeting record and ${mode} governance artifacts (decisions, actions, questions) based on its content.
19086
+
19087
+ **Meeting ID:** ${meetingId}
19088
+ **Meeting Title:** ${meetingTitle}
19089
+
19090
+ ---
19091
+ ${meetingContent}
19092
+ ---
19093
+
19094
+ Analyze this meeting thoroughly and ${mode} all relevant governance artifacts. Tag each artifact with "source:${meetingId}" for traceability back to this meeting.`;
19095
+ }
19096
+
19097
+ // src/analysis/analyze.ts
19098
+ async function analyzeMeeting(options) {
19099
+ const { marvinDir, meetingId, draft, persona: personaInput } = options;
19100
+ const config2 = getConfig(marvinDir);
19101
+ const personaId = resolvePersonaId(personaInput);
19102
+ const persona = getPersona(personaId);
19103
+ const plugin = resolvePlugin(config2.project.methodology);
19104
+ const registrations = plugin?.documentTypeRegistrations ?? [];
19105
+ const store = new DocumentStore(marvinDir, registrations);
19106
+ const meetingDoc = store.get(meetingId);
19107
+ if (!meetingDoc) {
19108
+ throw new Error(`Meeting ${meetingId} not found`);
19109
+ }
19110
+ if (meetingDoc.frontmatter.type !== "meeting") {
19111
+ throw new Error(`Document ${meetingId} is not a meeting (type: ${meetingDoc.frontmatter.type})`);
19112
+ }
19113
+ const createdArtifacts = [];
19114
+ if (!draft) {
19115
+ const originalCreate = store.create.bind(store);
19116
+ store.create = (type, frontmatter, content) => {
19117
+ const tags = frontmatter.tags ?? [];
19118
+ const sourceTag = `source:${meetingId}`;
19119
+ if (!tags.includes(sourceTag)) {
19120
+ tags.push(sourceTag);
19121
+ }
19122
+ const doc = originalCreate(type, { ...frontmatter, source: meetingId, tags }, content);
19123
+ createdArtifacts.push(doc.frontmatter.id);
19124
+ return doc;
19125
+ };
19126
+ }
19127
+ const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
19128
+ const mcpServer = createMarvinMcpServer(store, { pluginTools });
19129
+ const systemPrompt = buildAnalyzeSystemPrompt(persona, config2.project, draft);
19130
+ const userPrompt = buildAnalyzeUserPrompt(
19131
+ meetingId,
19132
+ meetingDoc.content,
19133
+ meetingDoc.frontmatter.title,
19134
+ draft
19135
+ );
19136
+ const spinner = ora4({ text: `Analyzing meeting ${meetingId}...`, color: "cyan" });
19137
+ spinner.start();
19138
+ try {
19139
+ const conversation = query5({
19140
+ prompt: userPrompt,
19141
+ options: {
19142
+ systemPrompt,
19143
+ mcpServers: { "marvin-governance": mcpServer },
19144
+ permissionMode: "acceptEdits",
19145
+ maxTurns: 10,
19146
+ tools: [],
19147
+ allowedTools: [
19148
+ "mcp__marvin-governance__create_decision",
19149
+ "mcp__marvin-governance__create_action",
19150
+ "mcp__marvin-governance__create_question",
19151
+ "mcp__marvin-governance__list_decisions",
19152
+ "mcp__marvin-governance__list_actions",
19153
+ "mcp__marvin-governance__list_questions"
19154
+ ]
19155
+ }
19156
+ });
19157
+ for await (const message of conversation) {
19158
+ handleAnalyzeMessage(message, spinner);
19159
+ }
19160
+ if (!draft && createdArtifacts.length > 0) {
19161
+ appendOutcomesToMeeting(store, meetingDoc.frontmatter.id, meetingDoc.content, createdArtifacts, store);
19162
+ }
19163
+ spinner.stop();
19164
+ if (draft) {
19165
+ console.log(chalk12.dim(`
19166
+ Draft proposal complete. No artifacts were created.`));
19167
+ console.log(chalk12.dim(`Use "marvin analyze ${meetingId} --no-draft" to create artifacts.`));
19168
+ } else {
19169
+ console.log(chalk12.green(`
19170
+ Created ${createdArtifacts.length} artifact${createdArtifacts.length === 1 ? "" : "s"} from meeting ${meetingId}`));
19171
+ if (createdArtifacts.length > 0) {
19172
+ console.log(chalk12.dim(` ${createdArtifacts.join(", ")}`));
19173
+ }
19174
+ }
19175
+ return { meetingId, artifacts: createdArtifacts, draft };
19176
+ } catch (err) {
19177
+ spinner.stop();
19178
+ throw err;
19179
+ }
19180
+ }
19181
+ function appendOutcomesToMeeting(store, meetingId, existingContent, artifacts, storeInstance) {
19182
+ const outcomeLines = artifacts.map((id) => {
19183
+ const doc = storeInstance.get(id);
19184
+ const title = doc ? doc.frontmatter.title : id;
19185
+ return `- ${id}: ${title}`;
19186
+ });
19187
+ const outcomesSection = `
19188
+
19189
+ ## Outcomes
19190
+ ${outcomeLines.join("\n")}`;
19191
+ const updatedContent = existingContent + outcomesSection;
19192
+ store.update(meetingId, {}, updatedContent);
19193
+ }
19194
+ function handleAnalyzeMessage(message, spinner) {
19195
+ switch (message.type) {
19196
+ case "assistant": {
19197
+ spinner.stop();
19198
+ const textBlocks = message.message.content.filter(
19199
+ (b) => b.type === "text"
19200
+ );
19201
+ if (textBlocks.length > 0) {
19202
+ console.log(
19203
+ chalk12.cyan("\nMarvin: ") + textBlocks.map((b) => b.text).join("\n")
19204
+ );
19205
+ }
19206
+ break;
19207
+ }
19208
+ case "system": {
19209
+ if (message.subtype === "init") {
19210
+ spinner.start("Analyzing...");
19211
+ }
19212
+ break;
19213
+ }
19214
+ case "result": {
19215
+ spinner.stop();
19216
+ if (message.subtype !== "success") {
19217
+ console.log(
19218
+ chalk12.red(`
19219
+ Analysis ended with error: ${message.subtype}`)
19220
+ );
19221
+ }
19222
+ break;
19223
+ }
19224
+ }
19225
+ }
19226
+
19227
+ // src/cli/commands/analyze.ts
19228
+ async function analyzeCommand(meetingId, options) {
19229
+ const project = loadProject();
19230
+ const marvinDir = project.marvinDir;
19231
+ const config2 = getConfig(marvinDir);
19232
+ const plugin = resolvePlugin(config2.project.methodology);
19233
+ const registrations = plugin?.documentTypeRegistrations ?? [];
19234
+ const store = new DocumentStore(marvinDir, registrations);
19235
+ const meetingDoc = store.get(meetingId);
19236
+ if (!meetingDoc) {
19237
+ console.log(chalk13.red(`Meeting ${meetingId} not found.`));
19238
+ console.log(chalk13.dim(`Use "marvin chat --as dm" to create meetings, or check the ID.`));
19239
+ return;
19240
+ }
19241
+ if (meetingDoc.frontmatter.type !== "meeting") {
19242
+ console.log(chalk13.red(`Document ${meetingId} is not a meeting (type: ${meetingDoc.frontmatter.type}).`));
19243
+ return;
19244
+ }
19245
+ const isDraft = options.draft !== false;
19246
+ const persona = options.as ?? "delivery-manager";
19247
+ console.log(chalk13.bold(`
19248
+ Analyzing meeting: ${meetingDoc.frontmatter.title}`));
19249
+ console.log(chalk13.dim(`Mode: ${isDraft ? "draft (propose only)" : "direct (create artifacts)"}`));
19250
+ console.log(chalk13.dim(`Persona: ${persona}
19251
+ `));
19252
+ await analyzeMeeting({
19253
+ marvinDir,
19254
+ meetingId,
19255
+ draft: isDraft,
19256
+ persona
19257
+ });
19258
+ }
19259
+
18931
19260
  // src/cli/program.ts
18932
19261
  function createProgram() {
18933
19262
  const program = new Command();
@@ -18973,6 +19302,9 @@ function createProgram() {
18973
19302
  program.command("clone <url> [directory]").description("Clone governance data from a remote repository").action(async (url2, directory) => {
18974
19303
  await cloneCommand(url2, directory);
18975
19304
  });
19305
+ program.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) => {
19306
+ await analyzeCommand(meetingId, options);
19307
+ });
18976
19308
  program.command("import <path>").description("Import documents or sources from external paths").option("--dry-run", "Preview without writing files").option(
18977
19309
  "--conflict <strategy>",
18978
19310
  "ID conflict strategy: renumber, skip, overwrite (default: renumber)"