mrvn-cli 0.1.3 → 0.2.0

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
@@ -6,7 +6,6 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // src/cli/program.ts
9
- import { createRequire } from "module";
10
9
  import { Command } from "commander";
11
10
 
12
11
  // src/cli/commands/init.ts
@@ -1722,10 +1721,10 @@ var nanoid = /^[a-zA-Z0-9_-]{21}$/;
1722
1721
  var duration = /^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/;
1723
1722
  var extendedDuration = /^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/;
1724
1723
  var guid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;
1725
- var uuid = (version3) => {
1726
- if (!version3)
1724
+ var uuid = (version2) => {
1725
+ if (!version2)
1727
1726
  return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;
1728
- return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version3}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);
1727
+ return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version2}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);
1729
1728
  };
1730
1729
  var uuid4 = /* @__PURE__ */ uuid(4);
1731
1730
  var uuid6 = /* @__PURE__ */ uuid(6);
@@ -13881,10 +13880,10 @@ function fromJSONSchema(schema, params) {
13881
13880
  if (typeof schema === "boolean") {
13882
13881
  return schema ? z.any() : z.never();
13883
13882
  }
13884
- const version3 = detectVersion(schema, params?.defaultTarget);
13883
+ const version2 = detectVersion(schema, params?.defaultTarget);
13885
13884
  const defs = schema.$defs || schema.definitions || {};
13886
13885
  const ctx = {
13887
- version: version3,
13886
+ version: version2,
13888
13887
  defs,
13889
13888
  refs: /* @__PURE__ */ new Map(),
13890
13889
  processing: /* @__PURE__ */ new Set(),
@@ -15928,7 +15927,7 @@ import * as readline from "readline";
15928
15927
  import chalk2 from "chalk";
15929
15928
  import ora from "ora";
15930
15929
  import {
15931
- query as query3
15930
+ query as query2
15932
15931
  } from "@anthropic-ai/claude-agent-sdk";
15933
15932
 
15934
15933
  // src/personas/prompt-builder.ts
@@ -16977,7 +16976,9 @@ var SourceManifestManager = class {
16977
16976
  // src/skills/registry.ts
16978
16977
  import * as fs8 from "fs";
16979
16978
  import * as path8 from "path";
16979
+ import { fileURLToPath } from "url";
16980
16980
  import * as YAML6 from "yaml";
16981
+ import matter2 from "gray-matter";
16981
16982
 
16982
16983
  // src/skills/builtin/governance-review.ts
16983
16984
  var governanceReviewSkill = {
@@ -16985,6 +16986,7 @@ var governanceReviewSkill = {
16985
16986
  name: "Governance Review",
16986
16987
  description: "Review open governance items and generate summaries",
16987
16988
  version: "1.0.0",
16989
+ format: "builtin-ts",
16988
16990
  personas: ["delivery-manager", "product-owner"],
16989
16991
  promptFragments: {
16990
16992
  "delivery-manager": `You have the **Governance Review** skill. You can proactively review all open governance items (decisions, actions, questions) and produce structured summaries with recommendations. Use the \`governance-review__summarize\` tool to run a comprehensive review.`,
@@ -17019,11 +17021,98 @@ Be thorough but concise. Focus on actionable insights.`,
17019
17021
  var BUILTIN_SKILLS = {
17020
17022
  "governance-review": governanceReviewSkill
17021
17023
  };
17024
+ var GOVERNANCE_TOOL_NAMES = [
17025
+ "mcp__marvin-governance__list_decisions",
17026
+ "mcp__marvin-governance__get_decision",
17027
+ "mcp__marvin-governance__create_decision",
17028
+ "mcp__marvin-governance__update_decision",
17029
+ "mcp__marvin-governance__list_actions",
17030
+ "mcp__marvin-governance__get_action",
17031
+ "mcp__marvin-governance__create_action",
17032
+ "mcp__marvin-governance__update_action",
17033
+ "mcp__marvin-governance__list_questions",
17034
+ "mcp__marvin-governance__get_question",
17035
+ "mcp__marvin-governance__create_question",
17036
+ "mcp__marvin-governance__update_question",
17037
+ "mcp__marvin-governance__search_documents",
17038
+ "mcp__marvin-governance__read_document",
17039
+ "mcp__marvin-governance__project_summary"
17040
+ ];
17041
+ function getBuiltinSkillsDir() {
17042
+ const thisFile = fileURLToPath(import.meta.url);
17043
+ return path8.join(path8.dirname(thisFile), "builtin");
17044
+ }
17045
+ function loadSkillFromDirectory(dirPath) {
17046
+ const skillMdPath = path8.join(dirPath, "SKILL.md");
17047
+ if (!fs8.existsSync(skillMdPath)) return void 0;
17048
+ try {
17049
+ const raw = fs8.readFileSync(skillMdPath, "utf-8");
17050
+ const { data, content } = matter2(raw);
17051
+ if (!data.name || !data.description) return void 0;
17052
+ const metadata = data.metadata ?? {};
17053
+ const version2 = metadata.version ?? "1.0.0";
17054
+ const personas = metadata.personas;
17055
+ const promptFragments = {};
17056
+ const wildcardPrompt = content.trim();
17057
+ if (wildcardPrompt) {
17058
+ promptFragments["*"] = wildcardPrompt;
17059
+ }
17060
+ const personasDir = path8.join(dirPath, "personas");
17061
+ if (fs8.existsSync(personasDir)) {
17062
+ try {
17063
+ for (const file2 of fs8.readdirSync(personasDir)) {
17064
+ if (!file2.endsWith(".md")) continue;
17065
+ const personaId = file2.replace(/\.md$/, "");
17066
+ const personaPrompt = fs8.readFileSync(path8.join(personasDir, file2), "utf-8").trim();
17067
+ if (personaPrompt) {
17068
+ promptFragments[personaId] = personaPrompt;
17069
+ }
17070
+ }
17071
+ } catch {
17072
+ }
17073
+ }
17074
+ let actions;
17075
+ const actionsPath = path8.join(dirPath, "actions.yaml");
17076
+ if (fs8.existsSync(actionsPath)) {
17077
+ try {
17078
+ const actionsRaw = fs8.readFileSync(actionsPath, "utf-8");
17079
+ actions = YAML6.parse(actionsRaw);
17080
+ } catch {
17081
+ }
17082
+ }
17083
+ return {
17084
+ id: data.name,
17085
+ name: data.name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
17086
+ description: data.description,
17087
+ version: version2,
17088
+ format: "skill-md",
17089
+ dirPath,
17090
+ personas,
17091
+ promptFragments: Object.keys(promptFragments).length > 0 ? promptFragments : void 0,
17092
+ actions
17093
+ };
17094
+ } catch {
17095
+ return void 0;
17096
+ }
17097
+ }
17022
17098
  function loadAllSkills(marvinDir) {
17023
17099
  const skills = /* @__PURE__ */ new Map();
17024
17100
  for (const [id, skill] of Object.entries(BUILTIN_SKILLS)) {
17025
17101
  skills.set(id, skill);
17026
17102
  }
17103
+ try {
17104
+ const builtinDir = getBuiltinSkillsDir();
17105
+ if (fs8.existsSync(builtinDir)) {
17106
+ for (const entry of fs8.readdirSync(builtinDir)) {
17107
+ const entryPath = path8.join(builtinDir, entry);
17108
+ if (!fs8.statSync(entryPath).isDirectory()) continue;
17109
+ if (skills.has(entry)) continue;
17110
+ const skill = loadSkillFromDirectory(entryPath);
17111
+ if (skill) skills.set(skill.id, skill);
17112
+ }
17113
+ }
17114
+ } catch {
17115
+ }
17027
17116
  if (marvinDir) {
17028
17117
  const skillsDir = path8.join(marvinDir, "skills");
17029
17118
  if (fs8.existsSync(skillsDir)) {
@@ -17033,10 +17122,20 @@ function loadAllSkills(marvinDir) {
17033
17122
  } catch {
17034
17123
  entries = [];
17035
17124
  }
17036
- for (const file2 of entries) {
17037
- if (!file2.endsWith(".yaml") && !file2.endsWith(".yml")) continue;
17125
+ for (const entry of entries) {
17126
+ const entryPath = path8.join(skillsDir, entry);
17127
+ try {
17128
+ if (fs8.statSync(entryPath).isDirectory()) {
17129
+ const skill = loadSkillFromDirectory(entryPath);
17130
+ if (skill) skills.set(skill.id, skill);
17131
+ continue;
17132
+ }
17133
+ } catch {
17134
+ continue;
17135
+ }
17136
+ if (!entry.endsWith(".yaml") && !entry.endsWith(".yml")) continue;
17038
17137
  try {
17039
- const raw = fs8.readFileSync(path8.join(skillsDir, file2), "utf-8");
17138
+ const raw = fs8.readFileSync(entryPath, "utf-8");
17040
17139
  const parsed = YAML6.parse(raw);
17041
17140
  if (!parsed?.id || !parsed?.name || !parsed?.version) continue;
17042
17141
  const skill = {
@@ -17044,6 +17143,7 @@ function loadAllSkills(marvinDir) {
17044
17143
  name: parsed.name,
17045
17144
  description: parsed.description ?? "",
17046
17145
  version: parsed.version,
17146
+ format: "yaml",
17047
17147
  personas: parsed.personas,
17048
17148
  promptFragments: parsed.promptFragments,
17049
17149
  actions: parsed.actions
@@ -17106,91 +17206,73 @@ function listAllSkillInfo(allSkills, skillsConfig, personaIds) {
17106
17206
  name: skill.name,
17107
17207
  version: skill.version,
17108
17208
  description: skill.description,
17209
+ format: skill.format,
17109
17210
  assignedPersonas
17110
17211
  });
17111
17212
  }
17112
17213
  return result;
17113
17214
  }
17114
-
17115
- // src/skills/action-tools.ts
17116
- import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
17117
-
17118
- // src/skills/action-runner.ts
17119
- import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
17120
- var GOVERNANCE_TOOL_NAMES = [
17121
- "mcp__marvin-governance__list_decisions",
17122
- "mcp__marvin-governance__get_decision",
17123
- "mcp__marvin-governance__create_decision",
17124
- "mcp__marvin-governance__update_decision",
17125
- "mcp__marvin-governance__list_actions",
17126
- "mcp__marvin-governance__get_action",
17127
- "mcp__marvin-governance__create_action",
17128
- "mcp__marvin-governance__update_action",
17129
- "mcp__marvin-governance__list_questions",
17130
- "mcp__marvin-governance__get_question",
17131
- "mcp__marvin-governance__create_question",
17132
- "mcp__marvin-governance__update_question",
17133
- "mcp__marvin-governance__search_documents",
17134
- "mcp__marvin-governance__read_document",
17135
- "mcp__marvin-governance__project_summary"
17136
- ];
17137
- async function runSkillAction(action, userPrompt, context) {
17138
- try {
17139
- const mcpServer = createMarvinMcpServer(context.store);
17140
- const allowedTools = action.allowGovernanceTools !== false ? GOVERNANCE_TOOL_NAMES : [];
17141
- const conversation = query2({
17142
- prompt: userPrompt,
17143
- options: {
17144
- systemPrompt: action.systemPrompt,
17145
- mcpServers: { "marvin-governance": mcpServer },
17215
+ function getSkillAgentDefinitions(skillIds, allSkills) {
17216
+ const agents = {};
17217
+ for (const id of skillIds) {
17218
+ const skill = allSkills.get(id);
17219
+ if (!skill?.actions) continue;
17220
+ for (const action of skill.actions) {
17221
+ const agentKey = `${skill.id}__${action.id}`;
17222
+ agents[agentKey] = {
17223
+ description: action.description,
17224
+ prompt: action.systemPrompt,
17146
17225
  maxTurns: action.maxTurns ?? 5,
17147
- allowedTools,
17148
- cwd: context.projectRoot,
17149
- tools: [],
17150
- permissionMode: "acceptEdits"
17151
- }
17152
- });
17153
- const textParts = [];
17154
- for await (const message of conversation) {
17155
- if (message.type === "assistant") {
17156
- const textBlocks = message.message.content.filter(
17157
- (b) => b.type === "text"
17226
+ tools: action.allowGovernanceTools !== false ? GOVERNANCE_TOOL_NAMES : []
17227
+ };
17228
+ }
17229
+ }
17230
+ return agents;
17231
+ }
17232
+ function migrateYamlToSkillMd(yamlPath, outputDir) {
17233
+ const raw = fs8.readFileSync(yamlPath, "utf-8");
17234
+ const parsed = YAML6.parse(raw);
17235
+ if (!parsed?.id || !parsed?.name) {
17236
+ throw new Error(`Invalid skill YAML: missing required fields (id, name)`);
17237
+ }
17238
+ fs8.mkdirSync(outputDir, { recursive: true });
17239
+ const frontmatter = {
17240
+ name: parsed.id,
17241
+ description: parsed.description ?? ""
17242
+ };
17243
+ const metadata = {};
17244
+ if (parsed.version) metadata.version = parsed.version;
17245
+ if (parsed.personas) metadata.personas = parsed.personas;
17246
+ if (Object.keys(metadata).length > 0) frontmatter.metadata = metadata;
17247
+ const promptFragments = parsed.promptFragments;
17248
+ const wildcardPrompt = promptFragments?.["*"] ?? "";
17249
+ const skillMd = matter2.stringify(wildcardPrompt ? `
17250
+ ${wildcardPrompt}
17251
+ ` : "\n", frontmatter);
17252
+ fs8.writeFileSync(path8.join(outputDir, "SKILL.md"), skillMd, "utf-8");
17253
+ if (promptFragments) {
17254
+ const personaKeys = Object.keys(promptFragments).filter((k) => k !== "*");
17255
+ if (personaKeys.length > 0) {
17256
+ const personasDir = path8.join(outputDir, "personas");
17257
+ fs8.mkdirSync(personasDir, { recursive: true });
17258
+ for (const personaId of personaKeys) {
17259
+ fs8.writeFileSync(
17260
+ path8.join(personasDir, `${personaId}.md`),
17261
+ `${promptFragments[personaId]}
17262
+ `,
17263
+ "utf-8"
17158
17264
  );
17159
- for (const block of textBlocks) {
17160
- textParts.push(block.text);
17161
- }
17162
17265
  }
17163
17266
  }
17164
- return {
17165
- content: [{ type: "text", text: textParts.join("\n") || "Action completed with no output." }]
17166
- };
17167
- } catch (err) {
17168
- return {
17169
- content: [{ type: "text", text: `Skill action failed: ${err}` }],
17170
- isError: true
17171
- };
17172
17267
  }
17173
- }
17174
-
17175
- // src/skills/action-tools.ts
17176
- function createSkillActionTools(skills, context) {
17177
- const tools = [];
17178
- for (const skill of skills) {
17179
- if (!skill.actions) continue;
17180
- for (const action of skill.actions) {
17181
- tools.push(
17182
- tool16(
17183
- `${skill.id}__${action.id}`,
17184
- action.description,
17185
- {
17186
- prompt: external_exports.string().describe("What you want this action to do")
17187
- },
17188
- async (args) => runSkillAction(action, args.prompt, context)
17189
- )
17190
- );
17191
- }
17268
+ const actions = parsed.actions;
17269
+ if (actions && actions.length > 0) {
17270
+ fs8.writeFileSync(
17271
+ path8.join(outputDir, "actions.yaml"),
17272
+ YAML6.stringify(actions),
17273
+ "utf-8"
17274
+ );
17192
17275
  }
17193
- return tools;
17194
17276
  }
17195
17277
 
17196
17278
  // src/agent/session.ts
@@ -17208,15 +17290,14 @@ async function startSession(options) {
17208
17290
  const allSkills = loadAllSkills(marvinDir);
17209
17291
  const skillIds = resolveSkillsForPersona(persona.id, config2.project.skills, allSkills);
17210
17292
  const codeSkillTools = getSkillTools(skillIds, allSkills, store);
17211
- const skillsWithActions = skillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
17212
- const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
17293
+ const skillAgents = getSkillAgentDefinitions(skillIds, allSkills);
17213
17294
  const skillPromptFragment = getSkillPromptFragment(skillIds, allSkills, persona.id);
17214
17295
  const mcpServer = createMarvinMcpServer(store, {
17215
17296
  manifest,
17216
17297
  sourcesDir: hasSourcesDir ? sourcesDir : void 0,
17217
17298
  sessionStore,
17218
17299
  pluginTools,
17219
- skillTools: [...codeSkillTools, ...actionTools]
17300
+ skillTools: codeSkillTools
17220
17301
  });
17221
17302
  const systemPrompt = buildSystemPrompt(persona, config2.project, pluginPromptFragment, skillPromptFragment);
17222
17303
  let existingSession;
@@ -17306,14 +17387,16 @@ Marvin \u2014 ${persona.name}
17306
17387
  "mcp__marvin-governance__get_session",
17307
17388
  "mcp__marvin-governance__analyze_meeting",
17308
17389
  ...pluginTools.map((t) => `mcp__marvin-governance__${t.name}`),
17309
- ...codeSkillTools.map((t) => `mcp__marvin-governance__${t.name}`),
17310
- ...actionTools.map((t) => `mcp__marvin-governance__${t.name}`)
17390
+ ...codeSkillTools.map((t) => `mcp__marvin-governance__${t.name}`)
17311
17391
  ]
17312
17392
  };
17393
+ if (Object.keys(skillAgents).length > 0) {
17394
+ queryOptions.agents = skillAgents;
17395
+ }
17313
17396
  if (existingSession) {
17314
17397
  queryOptions.resume = existingSession.id;
17315
17398
  }
17316
- const conversation = query3({
17399
+ const conversation = query2({
17317
17400
  prompt,
17318
17401
  options: queryOptions
17319
17402
  });
@@ -17635,7 +17718,7 @@ import * as fs10 from "fs";
17635
17718
  import * as path10 from "path";
17636
17719
  import chalk7 from "chalk";
17637
17720
  import ora2 from "ora";
17638
- import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
17721
+ import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
17639
17722
 
17640
17723
  // src/sources/prompts.ts
17641
17724
  function buildIngestSystemPrompt(persona, projectConfig, isDraft) {
@@ -17768,7 +17851,7 @@ async function ingestFile(options) {
17768
17851
  const spinner = ora2({ text: `Analyzing ${fileName}...`, color: "cyan" });
17769
17852
  spinner.start();
17770
17853
  try {
17771
- const conversation = query4({
17854
+ const conversation = query3({
17772
17855
  prompt: userPrompt,
17773
17856
  options: {
17774
17857
  systemPrompt,
@@ -17987,8 +18070,8 @@ var MarvinGit = class {
17987
18070
  );
17988
18071
  }
17989
18072
  await this.git.init();
17990
- const { writeFileSync: writeFileSync8 } = await import("fs");
17991
- writeFileSync8(
18073
+ const { writeFileSync: writeFileSync9 } = await import("fs");
18074
+ writeFileSync9(
17992
18075
  path12.join(this.marvinDir, ".gitignore"),
17993
18076
  MARVIN_GITIGNORE,
17994
18077
  "utf-8"
@@ -18293,6 +18376,89 @@ import * as fs12 from "fs";
18293
18376
  import * as path13 from "path";
18294
18377
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
18295
18378
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18379
+
18380
+ // src/skills/action-tools.ts
18381
+ import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
18382
+
18383
+ // src/skills/action-runner.ts
18384
+ import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
18385
+ var GOVERNANCE_TOOL_NAMES2 = [
18386
+ "mcp__marvin-governance__list_decisions",
18387
+ "mcp__marvin-governance__get_decision",
18388
+ "mcp__marvin-governance__create_decision",
18389
+ "mcp__marvin-governance__update_decision",
18390
+ "mcp__marvin-governance__list_actions",
18391
+ "mcp__marvin-governance__get_action",
18392
+ "mcp__marvin-governance__create_action",
18393
+ "mcp__marvin-governance__update_action",
18394
+ "mcp__marvin-governance__list_questions",
18395
+ "mcp__marvin-governance__get_question",
18396
+ "mcp__marvin-governance__create_question",
18397
+ "mcp__marvin-governance__update_question",
18398
+ "mcp__marvin-governance__search_documents",
18399
+ "mcp__marvin-governance__read_document",
18400
+ "mcp__marvin-governance__project_summary"
18401
+ ];
18402
+ async function runSkillAction(action, userPrompt, context) {
18403
+ try {
18404
+ const mcpServer = createMarvinMcpServer(context.store);
18405
+ const allowedTools = action.allowGovernanceTools !== false ? GOVERNANCE_TOOL_NAMES2 : [];
18406
+ const conversation = query4({
18407
+ prompt: userPrompt,
18408
+ options: {
18409
+ systemPrompt: action.systemPrompt,
18410
+ mcpServers: { "marvin-governance": mcpServer },
18411
+ maxTurns: action.maxTurns ?? 5,
18412
+ allowedTools,
18413
+ cwd: context.projectRoot,
18414
+ tools: [],
18415
+ permissionMode: "acceptEdits"
18416
+ }
18417
+ });
18418
+ const textParts = [];
18419
+ for await (const message of conversation) {
18420
+ if (message.type === "assistant") {
18421
+ const textBlocks = message.message.content.filter(
18422
+ (b) => b.type === "text"
18423
+ );
18424
+ for (const block of textBlocks) {
18425
+ textParts.push(block.text);
18426
+ }
18427
+ }
18428
+ }
18429
+ return {
18430
+ content: [{ type: "text", text: textParts.join("\n") || "Action completed with no output." }]
18431
+ };
18432
+ } catch (err) {
18433
+ return {
18434
+ content: [{ type: "text", text: `Skill action failed: ${err}` }],
18435
+ isError: true
18436
+ };
18437
+ }
18438
+ }
18439
+
18440
+ // src/skills/action-tools.ts
18441
+ function createSkillActionTools(skills, context) {
18442
+ const tools = [];
18443
+ for (const skill of skills) {
18444
+ if (!skill.actions) continue;
18445
+ for (const action of skill.actions) {
18446
+ tools.push(
18447
+ tool16(
18448
+ `${skill.id}__${action.id}`,
18449
+ action.description,
18450
+ {
18451
+ prompt: external_exports.string().describe("What you want this action to do")
18452
+ },
18453
+ async (args) => runSkillAction(action, args.prompt, context)
18454
+ )
18455
+ );
18456
+ }
18457
+ }
18458
+ return tools;
18459
+ }
18460
+
18461
+ // src/mcp/stdio-server.ts
18296
18462
  function collectTools(marvinDir) {
18297
18463
  const config2 = loadProjectConfig(marvinDir);
18298
18464
  const plugin = resolvePlugin(config2.methodology);
@@ -18370,6 +18536,7 @@ async function serveCommand() {
18370
18536
  import * as fs13 from "fs";
18371
18537
  import * as path14 from "path";
18372
18538
  import * as YAML7 from "yaml";
18539
+ import matter3 from "gray-matter";
18373
18540
  import chalk10 from "chalk";
18374
18541
  async function skillsListCommand() {
18375
18542
  const project = loadProject();
@@ -18383,10 +18550,12 @@ async function skillsListCommand() {
18383
18550
  }
18384
18551
  console.log(chalk10.bold("\nAvailable Skills\n"));
18385
18552
  const idWidth = Math.max(5, ...infos.map((s) => s.id.length));
18553
+ const fmtWidth = Math.max(6, ...infos.map((s) => s.format.length));
18386
18554
  const verWidth = Math.max(7, ...infos.map((s) => s.version.length));
18387
18555
  const descWidth = Math.max(11, ...infos.map((s) => s.description.length));
18388
18556
  const header = [
18389
18557
  "ID".padEnd(idWidth),
18558
+ "Format".padEnd(fmtWidth),
18390
18559
  "Version".padEnd(verWidth),
18391
18560
  "Description".padEnd(descWidth),
18392
18561
  "Personas"
@@ -18398,6 +18567,7 @@ async function skillsListCommand() {
18398
18567
  console.log(
18399
18568
  [
18400
18569
  info.id.padEnd(idWidth),
18570
+ info.format.padEnd(fmtWidth),
18401
18571
  info.version.padEnd(verWidth),
18402
18572
  info.description.padEnd(descWidth),
18403
18573
  personas
@@ -18466,33 +18636,82 @@ async function skillsCreateCommand(name) {
18466
18636
  const project = loadProject();
18467
18637
  const skillsDir = path14.join(project.marvinDir, "skills");
18468
18638
  fs13.mkdirSync(skillsDir, { recursive: true });
18469
- const filePath = path14.join(skillsDir, `${name}.yaml`);
18470
- if (fs13.existsSync(filePath)) {
18471
- console.log(chalk10.yellow(`Skill file already exists: ${filePath}`));
18639
+ const skillDir = path14.join(skillsDir, name);
18640
+ if (fs13.existsSync(skillDir)) {
18641
+ console.log(chalk10.yellow(`Skill directory already exists: ${skillDir}`));
18472
18642
  return;
18473
18643
  }
18474
- const template = {
18475
- id: name,
18476
- name: name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
18644
+ fs13.mkdirSync(skillDir, { recursive: true });
18645
+ const displayName = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
18646
+ const frontmatter = {
18647
+ name,
18477
18648
  description: `Custom skill: ${name}`,
18478
- version: "1.0.0",
18479
- personas: ["product-owner"],
18480
- promptFragments: {
18481
- "*": `You have the **${name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}** skill.`
18482
- },
18483
- actions: [
18484
- {
18485
- id: "run",
18486
- name: `Run ${name}`,
18487
- description: `Execute the ${name} skill`,
18488
- systemPrompt: "You are a helpful assistant. Complete the requested task using the available governance tools.",
18489
- maxTurns: 5
18490
- }
18491
- ]
18649
+ metadata: {
18650
+ version: "1.0.0",
18651
+ personas: ["product-owner"]
18652
+ }
18492
18653
  };
18493
- fs13.writeFileSync(filePath, YAML7.stringify(template), "utf-8");
18494
- console.log(chalk10.green(`Created skill template: ${filePath}`));
18495
- console.log(chalk10.dim("Edit the YAML file to customize your skill."));
18654
+ const body = `
18655
+ You have the **${displayName}** skill.
18656
+ `;
18657
+ const skillMd = matter3.stringify(body, frontmatter);
18658
+ fs13.writeFileSync(path14.join(skillDir, "SKILL.md"), skillMd, "utf-8");
18659
+ const actions = [
18660
+ {
18661
+ id: "run",
18662
+ name: `Run ${name}`,
18663
+ description: `Execute the ${name} skill`,
18664
+ systemPrompt: "You are a helpful assistant. Complete the requested task using the available governance tools.",
18665
+ maxTurns: 5
18666
+ }
18667
+ ];
18668
+ fs13.writeFileSync(path14.join(skillDir, "actions.yaml"), YAML7.stringify(actions), "utf-8");
18669
+ console.log(chalk10.green(`Created skill: ${skillDir}/`));
18670
+ console.log(chalk10.dim(" SKILL.md \u2014 skill definition and prompt"));
18671
+ console.log(chalk10.dim(" actions.yaml \u2014 action definitions"));
18672
+ console.log(chalk10.dim("\nAdd persona-specific prompts in personas/<persona-id>.md"));
18673
+ }
18674
+ async function skillsMigrateCommand() {
18675
+ const project = loadProject();
18676
+ const skillsDir = path14.join(project.marvinDir, "skills");
18677
+ if (!fs13.existsSync(skillsDir)) {
18678
+ console.log(chalk10.dim("No skills directory found."));
18679
+ return;
18680
+ }
18681
+ let entries;
18682
+ try {
18683
+ entries = fs13.readdirSync(skillsDir);
18684
+ } catch {
18685
+ console.log(chalk10.red("Could not read skills directory."));
18686
+ return;
18687
+ }
18688
+ const yamlFiles = entries.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
18689
+ if (yamlFiles.length === 0) {
18690
+ console.log(chalk10.dim("No YAML skill files to migrate."));
18691
+ return;
18692
+ }
18693
+ let migrated = 0;
18694
+ for (const file2 of yamlFiles) {
18695
+ const yamlPath = path14.join(skillsDir, file2);
18696
+ const baseName = file2.replace(/\.(yaml|yml)$/, "");
18697
+ const outputDir = path14.join(skillsDir, baseName);
18698
+ if (fs13.existsSync(outputDir)) {
18699
+ console.log(chalk10.yellow(`Skipping "${file2}" \u2014 directory "${baseName}/" already exists.`));
18700
+ continue;
18701
+ }
18702
+ try {
18703
+ migrateYamlToSkillMd(yamlPath, outputDir);
18704
+ fs13.renameSync(yamlPath, `${yamlPath}.bak`);
18705
+ console.log(chalk10.green(`Migrated "${file2}" \u2192 "${baseName}/"`));
18706
+ migrated++;
18707
+ } catch (err) {
18708
+ console.log(chalk10.red(`Failed to migrate "${file2}": ${err}`));
18709
+ }
18710
+ }
18711
+ if (migrated > 0) {
18712
+ console.log(chalk10.dim(`
18713
+ ${migrated} skill(s) migrated. Original files renamed to *.bak`));
18714
+ }
18496
18715
  }
18497
18716
 
18498
18717
  // src/cli/commands/import.ts
@@ -18503,12 +18722,12 @@ import chalk11 from "chalk";
18503
18722
  // src/import/engine.ts
18504
18723
  import * as fs15 from "fs";
18505
18724
  import * as path16 from "path";
18506
- import matter3 from "gray-matter";
18725
+ import matter5 from "gray-matter";
18507
18726
 
18508
18727
  // src/import/classifier.ts
18509
18728
  import * as fs14 from "fs";
18510
18729
  import * as path15 from "path";
18511
- import matter2 from "gray-matter";
18730
+ import matter4 from "gray-matter";
18512
18731
  var RAW_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".txt"]);
18513
18732
  var CORE_DIR_NAMES = /* @__PURE__ */ new Set(["decisions", "actions", "questions"]);
18514
18733
  var ID_PATTERN = /^[A-Z]+-\d{3,}$/;
@@ -18534,7 +18753,7 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
18534
18753
  const hasMarvinDocs = mdFiles.some((f) => {
18535
18754
  try {
18536
18755
  const raw = fs14.readFileSync(path15.join(resolved, f), "utf-8");
18537
- const { data } = matter2(raw);
18756
+ const { data } = matter4(raw);
18538
18757
  return isValidMarvinDocument(data, knownTypes);
18539
18758
  } catch {
18540
18759
  return false;
@@ -18555,7 +18774,7 @@ function classifyFile(filePath, knownTypes) {
18555
18774
  if (ext === ".md") {
18556
18775
  try {
18557
18776
  const raw = fs14.readFileSync(resolved, "utf-8");
18558
- const { data } = matter2(raw);
18777
+ const { data } = matter4(raw);
18559
18778
  if (isValidMarvinDocument(data, knownTypes)) {
18560
18779
  return { type: "marvin-document", inputPath: resolved };
18561
18780
  }
@@ -18766,7 +18985,7 @@ function collectMarvinDocs(dir, knownTypes) {
18766
18985
  const filePath = path16.join(dir, file2);
18767
18986
  try {
18768
18987
  const raw = fs15.readFileSync(filePath, "utf-8");
18769
- const { data, content } = matter3(raw);
18988
+ const { data, content } = matter5(raw);
18770
18989
  if (isValidMarvinDocument(data, knownTypes)) {
18771
18990
  docs.push({
18772
18991
  frontmatter: data,
@@ -18860,7 +19079,7 @@ function planFromSingleDocument(classification, store, _marvinDir, options) {
18860
19079
  const filePath = classification.inputPath;
18861
19080
  const knownTypes = store.registeredTypes;
18862
19081
  const raw = fs15.readFileSync(filePath, "utf-8");
18863
- const { data, content } = matter3(raw);
19082
+ const { data, content } = matter5(raw);
18864
19083
  if (!isValidMarvinDocument(data, knownTypes)) {
18865
19084
  return [];
18866
19085
  }
@@ -19253,13 +19472,11 @@ Analyzing meeting: ${meetingDoc.frontmatter.title}`));
19253
19472
  }
19254
19473
 
19255
19474
  // src/cli/program.ts
19256
- var require2 = createRequire(import.meta.url);
19257
- var { version: version2 } = require2("../../package.json");
19258
19475
  function createProgram() {
19259
19476
  const program2 = new Command();
19260
19477
  program2.name("marvin").description(
19261
19478
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
19262
- ).version(version2);
19479
+ ).version("0.2.0");
19263
19480
  program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
19264
19481
  await initCommand();
19265
19482
  });
@@ -19320,9 +19537,12 @@ function createProgram() {
19320
19537
  skillsCmd.command("remove <skill>").description("Unassign a skill from a persona").requiredOption("--as <persona>", "Persona to remove the skill from").action(async (skill, options) => {
19321
19538
  await skillsRemoveCommand(skill, options);
19322
19539
  });
19323
- skillsCmd.command("create <name>").description("Create a YAML skill template in .marvin/skills/").action(async (name) => {
19540
+ skillsCmd.command("create <name>").description("Create a new skill in .marvin/skills/ (SKILL.md format)").action(async (name) => {
19324
19541
  await skillsCreateCommand(name);
19325
19542
  });
19543
+ skillsCmd.command("migrate").description("Migrate YAML skill files to SKILL.md directory format").action(async () => {
19544
+ await skillsMigrateCommand();
19545
+ });
19326
19546
  return program2;
19327
19547
  }
19328
19548