mrvn-cli 0.1.4 → 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
@@ -15927,7 +15927,7 @@ import * as readline from "readline";
15927
15927
  import chalk2 from "chalk";
15928
15928
  import ora from "ora";
15929
15929
  import {
15930
- query as query3
15930
+ query as query2
15931
15931
  } from "@anthropic-ai/claude-agent-sdk";
15932
15932
 
15933
15933
  // src/personas/prompt-builder.ts
@@ -16976,7 +16976,9 @@ var SourceManifestManager = class {
16976
16976
  // src/skills/registry.ts
16977
16977
  import * as fs8 from "fs";
16978
16978
  import * as path8 from "path";
16979
+ import { fileURLToPath } from "url";
16979
16980
  import * as YAML6 from "yaml";
16981
+ import matter2 from "gray-matter";
16980
16982
 
16981
16983
  // src/skills/builtin/governance-review.ts
16982
16984
  var governanceReviewSkill = {
@@ -16984,6 +16986,7 @@ var governanceReviewSkill = {
16984
16986
  name: "Governance Review",
16985
16987
  description: "Review open governance items and generate summaries",
16986
16988
  version: "1.0.0",
16989
+ format: "builtin-ts",
16987
16990
  personas: ["delivery-manager", "product-owner"],
16988
16991
  promptFragments: {
16989
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.`,
@@ -17018,11 +17021,98 @@ Be thorough but concise. Focus on actionable insights.`,
17018
17021
  var BUILTIN_SKILLS = {
17019
17022
  "governance-review": governanceReviewSkill
17020
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
+ }
17021
17098
  function loadAllSkills(marvinDir) {
17022
17099
  const skills = /* @__PURE__ */ new Map();
17023
17100
  for (const [id, skill] of Object.entries(BUILTIN_SKILLS)) {
17024
17101
  skills.set(id, skill);
17025
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
+ }
17026
17116
  if (marvinDir) {
17027
17117
  const skillsDir = path8.join(marvinDir, "skills");
17028
17118
  if (fs8.existsSync(skillsDir)) {
@@ -17032,10 +17122,20 @@ function loadAllSkills(marvinDir) {
17032
17122
  } catch {
17033
17123
  entries = [];
17034
17124
  }
17035
- for (const file2 of entries) {
17036
- 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;
17037
17137
  try {
17038
- const raw = fs8.readFileSync(path8.join(skillsDir, file2), "utf-8");
17138
+ const raw = fs8.readFileSync(entryPath, "utf-8");
17039
17139
  const parsed = YAML6.parse(raw);
17040
17140
  if (!parsed?.id || !parsed?.name || !parsed?.version) continue;
17041
17141
  const skill = {
@@ -17043,6 +17143,7 @@ function loadAllSkills(marvinDir) {
17043
17143
  name: parsed.name,
17044
17144
  description: parsed.description ?? "",
17045
17145
  version: parsed.version,
17146
+ format: "yaml",
17046
17147
  personas: parsed.personas,
17047
17148
  promptFragments: parsed.promptFragments,
17048
17149
  actions: parsed.actions
@@ -17105,91 +17206,73 @@ function listAllSkillInfo(allSkills, skillsConfig, personaIds) {
17105
17206
  name: skill.name,
17106
17207
  version: skill.version,
17107
17208
  description: skill.description,
17209
+ format: skill.format,
17108
17210
  assignedPersonas
17109
17211
  });
17110
17212
  }
17111
17213
  return result;
17112
17214
  }
17113
-
17114
- // src/skills/action-tools.ts
17115
- import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
17116
-
17117
- // src/skills/action-runner.ts
17118
- import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
17119
- var GOVERNANCE_TOOL_NAMES = [
17120
- "mcp__marvin-governance__list_decisions",
17121
- "mcp__marvin-governance__get_decision",
17122
- "mcp__marvin-governance__create_decision",
17123
- "mcp__marvin-governance__update_decision",
17124
- "mcp__marvin-governance__list_actions",
17125
- "mcp__marvin-governance__get_action",
17126
- "mcp__marvin-governance__create_action",
17127
- "mcp__marvin-governance__update_action",
17128
- "mcp__marvin-governance__list_questions",
17129
- "mcp__marvin-governance__get_question",
17130
- "mcp__marvin-governance__create_question",
17131
- "mcp__marvin-governance__update_question",
17132
- "mcp__marvin-governance__search_documents",
17133
- "mcp__marvin-governance__read_document",
17134
- "mcp__marvin-governance__project_summary"
17135
- ];
17136
- async function runSkillAction(action, userPrompt, context) {
17137
- try {
17138
- const mcpServer = createMarvinMcpServer(context.store);
17139
- const allowedTools = action.allowGovernanceTools !== false ? GOVERNANCE_TOOL_NAMES : [];
17140
- const conversation = query2({
17141
- prompt: userPrompt,
17142
- options: {
17143
- systemPrompt: action.systemPrompt,
17144
- 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,
17145
17225
  maxTurns: action.maxTurns ?? 5,
17146
- allowedTools,
17147
- cwd: context.projectRoot,
17148
- tools: [],
17149
- permissionMode: "acceptEdits"
17150
- }
17151
- });
17152
- const textParts = [];
17153
- for await (const message of conversation) {
17154
- if (message.type === "assistant") {
17155
- const textBlocks = message.message.content.filter(
17156
- (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"
17157
17264
  );
17158
- for (const block of textBlocks) {
17159
- textParts.push(block.text);
17160
- }
17161
17265
  }
17162
17266
  }
17163
- return {
17164
- content: [{ type: "text", text: textParts.join("\n") || "Action completed with no output." }]
17165
- };
17166
- } catch (err) {
17167
- return {
17168
- content: [{ type: "text", text: `Skill action failed: ${err}` }],
17169
- isError: true
17170
- };
17171
17267
  }
17172
- }
17173
-
17174
- // src/skills/action-tools.ts
17175
- function createSkillActionTools(skills, context) {
17176
- const tools = [];
17177
- for (const skill of skills) {
17178
- if (!skill.actions) continue;
17179
- for (const action of skill.actions) {
17180
- tools.push(
17181
- tool16(
17182
- `${skill.id}__${action.id}`,
17183
- action.description,
17184
- {
17185
- prompt: external_exports.string().describe("What you want this action to do")
17186
- },
17187
- async (args) => runSkillAction(action, args.prompt, context)
17188
- )
17189
- );
17190
- }
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
+ );
17191
17275
  }
17192
- return tools;
17193
17276
  }
17194
17277
 
17195
17278
  // src/agent/session.ts
@@ -17207,15 +17290,14 @@ async function startSession(options) {
17207
17290
  const allSkills = loadAllSkills(marvinDir);
17208
17291
  const skillIds = resolveSkillsForPersona(persona.id, config2.project.skills, allSkills);
17209
17292
  const codeSkillTools = getSkillTools(skillIds, allSkills, store);
17210
- const skillsWithActions = skillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
17211
- const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
17293
+ const skillAgents = getSkillAgentDefinitions(skillIds, allSkills);
17212
17294
  const skillPromptFragment = getSkillPromptFragment(skillIds, allSkills, persona.id);
17213
17295
  const mcpServer = createMarvinMcpServer(store, {
17214
17296
  manifest,
17215
17297
  sourcesDir: hasSourcesDir ? sourcesDir : void 0,
17216
17298
  sessionStore,
17217
17299
  pluginTools,
17218
- skillTools: [...codeSkillTools, ...actionTools]
17300
+ skillTools: codeSkillTools
17219
17301
  });
17220
17302
  const systemPrompt = buildSystemPrompt(persona, config2.project, pluginPromptFragment, skillPromptFragment);
17221
17303
  let existingSession;
@@ -17305,14 +17387,16 @@ Marvin \u2014 ${persona.name}
17305
17387
  "mcp__marvin-governance__get_session",
17306
17388
  "mcp__marvin-governance__analyze_meeting",
17307
17389
  ...pluginTools.map((t) => `mcp__marvin-governance__${t.name}`),
17308
- ...codeSkillTools.map((t) => `mcp__marvin-governance__${t.name}`),
17309
- ...actionTools.map((t) => `mcp__marvin-governance__${t.name}`)
17390
+ ...codeSkillTools.map((t) => `mcp__marvin-governance__${t.name}`)
17310
17391
  ]
17311
17392
  };
17393
+ if (Object.keys(skillAgents).length > 0) {
17394
+ queryOptions.agents = skillAgents;
17395
+ }
17312
17396
  if (existingSession) {
17313
17397
  queryOptions.resume = existingSession.id;
17314
17398
  }
17315
- const conversation = query3({
17399
+ const conversation = query2({
17316
17400
  prompt,
17317
17401
  options: queryOptions
17318
17402
  });
@@ -17634,7 +17718,7 @@ import * as fs10 from "fs";
17634
17718
  import * as path10 from "path";
17635
17719
  import chalk7 from "chalk";
17636
17720
  import ora2 from "ora";
17637
- import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
17721
+ import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
17638
17722
 
17639
17723
  // src/sources/prompts.ts
17640
17724
  function buildIngestSystemPrompt(persona, projectConfig, isDraft) {
@@ -17767,7 +17851,7 @@ async function ingestFile(options) {
17767
17851
  const spinner = ora2({ text: `Analyzing ${fileName}...`, color: "cyan" });
17768
17852
  spinner.start();
17769
17853
  try {
17770
- const conversation = query4({
17854
+ const conversation = query3({
17771
17855
  prompt: userPrompt,
17772
17856
  options: {
17773
17857
  systemPrompt,
@@ -17986,8 +18070,8 @@ var MarvinGit = class {
17986
18070
  );
17987
18071
  }
17988
18072
  await this.git.init();
17989
- const { writeFileSync: writeFileSync8 } = await import("fs");
17990
- writeFileSync8(
18073
+ const { writeFileSync: writeFileSync9 } = await import("fs");
18074
+ writeFileSync9(
17991
18075
  path12.join(this.marvinDir, ".gitignore"),
17992
18076
  MARVIN_GITIGNORE,
17993
18077
  "utf-8"
@@ -18292,6 +18376,89 @@ import * as fs12 from "fs";
18292
18376
  import * as path13 from "path";
18293
18377
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
18294
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
18295
18462
  function collectTools(marvinDir) {
18296
18463
  const config2 = loadProjectConfig(marvinDir);
18297
18464
  const plugin = resolvePlugin(config2.methodology);
@@ -18369,6 +18536,7 @@ async function serveCommand() {
18369
18536
  import * as fs13 from "fs";
18370
18537
  import * as path14 from "path";
18371
18538
  import * as YAML7 from "yaml";
18539
+ import matter3 from "gray-matter";
18372
18540
  import chalk10 from "chalk";
18373
18541
  async function skillsListCommand() {
18374
18542
  const project = loadProject();
@@ -18382,10 +18550,12 @@ async function skillsListCommand() {
18382
18550
  }
18383
18551
  console.log(chalk10.bold("\nAvailable Skills\n"));
18384
18552
  const idWidth = Math.max(5, ...infos.map((s) => s.id.length));
18553
+ const fmtWidth = Math.max(6, ...infos.map((s) => s.format.length));
18385
18554
  const verWidth = Math.max(7, ...infos.map((s) => s.version.length));
18386
18555
  const descWidth = Math.max(11, ...infos.map((s) => s.description.length));
18387
18556
  const header = [
18388
18557
  "ID".padEnd(idWidth),
18558
+ "Format".padEnd(fmtWidth),
18389
18559
  "Version".padEnd(verWidth),
18390
18560
  "Description".padEnd(descWidth),
18391
18561
  "Personas"
@@ -18397,6 +18567,7 @@ async function skillsListCommand() {
18397
18567
  console.log(
18398
18568
  [
18399
18569
  info.id.padEnd(idWidth),
18570
+ info.format.padEnd(fmtWidth),
18400
18571
  info.version.padEnd(verWidth),
18401
18572
  info.description.padEnd(descWidth),
18402
18573
  personas
@@ -18465,33 +18636,82 @@ async function skillsCreateCommand(name) {
18465
18636
  const project = loadProject();
18466
18637
  const skillsDir = path14.join(project.marvinDir, "skills");
18467
18638
  fs13.mkdirSync(skillsDir, { recursive: true });
18468
- const filePath = path14.join(skillsDir, `${name}.yaml`);
18469
- if (fs13.existsSync(filePath)) {
18470
- 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}`));
18471
18642
  return;
18472
18643
  }
18473
- const template = {
18474
- id: name,
18475
- 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,
18476
18648
  description: `Custom skill: ${name}`,
18477
- version: "1.0.0",
18478
- personas: ["product-owner"],
18479
- promptFragments: {
18480
- "*": `You have the **${name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}** skill.`
18481
- },
18482
- actions: [
18483
- {
18484
- id: "run",
18485
- name: `Run ${name}`,
18486
- description: `Execute the ${name} skill`,
18487
- systemPrompt: "You are a helpful assistant. Complete the requested task using the available governance tools.",
18488
- maxTurns: 5
18489
- }
18490
- ]
18649
+ metadata: {
18650
+ version: "1.0.0",
18651
+ personas: ["product-owner"]
18652
+ }
18491
18653
  };
18492
- fs13.writeFileSync(filePath, YAML7.stringify(template), "utf-8");
18493
- console.log(chalk10.green(`Created skill template: ${filePath}`));
18494
- 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
+ }
18495
18715
  }
18496
18716
 
18497
18717
  // src/cli/commands/import.ts
@@ -18502,12 +18722,12 @@ import chalk11 from "chalk";
18502
18722
  // src/import/engine.ts
18503
18723
  import * as fs15 from "fs";
18504
18724
  import * as path16 from "path";
18505
- import matter3 from "gray-matter";
18725
+ import matter5 from "gray-matter";
18506
18726
 
18507
18727
  // src/import/classifier.ts
18508
18728
  import * as fs14 from "fs";
18509
18729
  import * as path15 from "path";
18510
- import matter2 from "gray-matter";
18730
+ import matter4 from "gray-matter";
18511
18731
  var RAW_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".txt"]);
18512
18732
  var CORE_DIR_NAMES = /* @__PURE__ */ new Set(["decisions", "actions", "questions"]);
18513
18733
  var ID_PATTERN = /^[A-Z]+-\d{3,}$/;
@@ -18533,7 +18753,7 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
18533
18753
  const hasMarvinDocs = mdFiles.some((f) => {
18534
18754
  try {
18535
18755
  const raw = fs14.readFileSync(path15.join(resolved, f), "utf-8");
18536
- const { data } = matter2(raw);
18756
+ const { data } = matter4(raw);
18537
18757
  return isValidMarvinDocument(data, knownTypes);
18538
18758
  } catch {
18539
18759
  return false;
@@ -18554,7 +18774,7 @@ function classifyFile(filePath, knownTypes) {
18554
18774
  if (ext === ".md") {
18555
18775
  try {
18556
18776
  const raw = fs14.readFileSync(resolved, "utf-8");
18557
- const { data } = matter2(raw);
18777
+ const { data } = matter4(raw);
18558
18778
  if (isValidMarvinDocument(data, knownTypes)) {
18559
18779
  return { type: "marvin-document", inputPath: resolved };
18560
18780
  }
@@ -18765,7 +18985,7 @@ function collectMarvinDocs(dir, knownTypes) {
18765
18985
  const filePath = path16.join(dir, file2);
18766
18986
  try {
18767
18987
  const raw = fs15.readFileSync(filePath, "utf-8");
18768
- const { data, content } = matter3(raw);
18988
+ const { data, content } = matter5(raw);
18769
18989
  if (isValidMarvinDocument(data, knownTypes)) {
18770
18990
  docs.push({
18771
18991
  frontmatter: data,
@@ -18859,7 +19079,7 @@ function planFromSingleDocument(classification, store, _marvinDir, options) {
18859
19079
  const filePath = classification.inputPath;
18860
19080
  const knownTypes = store.registeredTypes;
18861
19081
  const raw = fs15.readFileSync(filePath, "utf-8");
18862
- const { data, content } = matter3(raw);
19082
+ const { data, content } = matter5(raw);
18863
19083
  if (!isValidMarvinDocument(data, knownTypes)) {
18864
19084
  return [];
18865
19085
  }
@@ -19256,7 +19476,7 @@ function createProgram() {
19256
19476
  const program2 = new Command();
19257
19477
  program2.name("marvin").description(
19258
19478
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
19259
- ).version("0.1.4");
19479
+ ).version("0.2.0");
19260
19480
  program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
19261
19481
  await initCommand();
19262
19482
  });
@@ -19317,9 +19537,12 @@ function createProgram() {
19317
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) => {
19318
19538
  await skillsRemoveCommand(skill, options);
19319
19539
  });
19320
- 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) => {
19321
19541
  await skillsCreateCommand(name);
19322
19542
  });
19543
+ skillsCmd.command("migrate").description("Migrate YAML skill files to SKILL.md directory format").action(async () => {
19544
+ await skillsMigrateCommand();
19545
+ });
19323
19546
  return program2;
19324
19547
  }
19325
19548