mrvn-cli 0.3.5 → 0.3.7

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
@@ -526,9 +526,24 @@ function resolvePersonaId(input4) {
526
526
  }
527
527
 
528
528
  // src/personas/prompt-builder.ts
529
- function buildSystemPrompt(persona, projectConfig, pluginPromptFragment, skillPromptFragment) {
529
+ import * as fs4 from "fs";
530
+ import * as path4 from "path";
531
+ function buildSystemPrompt(persona, projectConfig, pluginPromptFragment, skillPromptFragment, marvinDir) {
530
532
  const parts = [];
531
533
  parts.push(persona.systemPrompt);
534
+ if (marvinDir) {
535
+ const claudeMdPath = path4.join(marvinDir, "CLAUDE.md");
536
+ try {
537
+ const content = fs4.readFileSync(claudeMdPath, "utf-8").trim();
538
+ if (content) {
539
+ parts.push(`
540
+ ## Project Instructions
541
+ ${content}
542
+ `);
543
+ }
544
+ } catch {
545
+ }
546
+ }
532
547
  parts.push(`
533
548
  ## Project Context
534
549
  - **Project Name:** ${projectConfig.name}
@@ -1346,10 +1361,10 @@ function mergeDefs(...defs) {
1346
1361
  function cloneDef(schema) {
1347
1362
  return mergeDefs(schema._zod.def);
1348
1363
  }
1349
- function getElementAtPath(obj, path18) {
1350
- if (!path18)
1364
+ function getElementAtPath(obj, path20) {
1365
+ if (!path20)
1351
1366
  return obj;
1352
- return path18.reduce((acc, key) => acc?.[key], obj);
1367
+ return path20.reduce((acc, key) => acc?.[key], obj);
1353
1368
  }
1354
1369
  function promiseAllObject(promisesObj) {
1355
1370
  const keys = Object.keys(promisesObj);
@@ -1732,11 +1747,11 @@ function aborted(x, startIndex = 0) {
1732
1747
  }
1733
1748
  return false;
1734
1749
  }
1735
- function prefixIssues(path18, issues) {
1750
+ function prefixIssues(path20, issues) {
1736
1751
  return issues.map((iss) => {
1737
1752
  var _a2;
1738
1753
  (_a2 = iss).path ?? (_a2.path = []);
1739
- iss.path.unshift(path18);
1754
+ iss.path.unshift(path20);
1740
1755
  return iss;
1741
1756
  });
1742
1757
  }
@@ -1919,7 +1934,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
1919
1934
  }
1920
1935
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
1921
1936
  const result = { errors: [] };
1922
- const processError = (error49, path18 = []) => {
1937
+ const processError = (error49, path20 = []) => {
1923
1938
  var _a2, _b;
1924
1939
  for (const issue2 of error49.issues) {
1925
1940
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -1929,7 +1944,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
1929
1944
  } else if (issue2.code === "invalid_element") {
1930
1945
  processError({ issues: issue2.issues }, issue2.path);
1931
1946
  } else {
1932
- const fullpath = [...path18, ...issue2.path];
1947
+ const fullpath = [...path20, ...issue2.path];
1933
1948
  if (fullpath.length === 0) {
1934
1949
  result.errors.push(mapper(issue2));
1935
1950
  continue;
@@ -1961,8 +1976,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
1961
1976
  }
1962
1977
  function toDotPath(_path) {
1963
1978
  const segs = [];
1964
- const path18 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1965
- for (const seg of path18) {
1979
+ const path20 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1980
+ for (const seg of path20) {
1966
1981
  if (typeof seg === "number")
1967
1982
  segs.push(`[${seg}]`);
1968
1983
  else if (typeof seg === "symbol")
@@ -13939,13 +13954,13 @@ function resolveRef(ref, ctx) {
13939
13954
  if (!ref.startsWith("#")) {
13940
13955
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
13941
13956
  }
13942
- const path18 = ref.slice(1).split("/").filter(Boolean);
13943
- if (path18.length === 0) {
13957
+ const path20 = ref.slice(1).split("/").filter(Boolean);
13958
+ if (path20.length === 0) {
13944
13959
  return ctx.rootSchema;
13945
13960
  }
13946
13961
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
13947
- if (path18[0] === defsKey) {
13948
- const key = path18[1];
13962
+ if (path20[0] === defsKey) {
13963
+ const key = path20[1];
13949
13964
  if (!key || !ctx.defs[key]) {
13950
13965
  throw new Error(`Reference not found: ${ref}`);
13951
13966
  }
@@ -14435,7 +14450,8 @@ function createDecisionTools(store) {
14435
14450
  title: external_exports.string().optional().describe("New title"),
14436
14451
  status: external_exports.string().optional().describe("New status"),
14437
14452
  content: external_exports.string().optional().describe("New content"),
14438
- owner: external_exports.string().optional().describe("New owner")
14453
+ owner: external_exports.string().optional().describe("New owner"),
14454
+ tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
14439
14455
  },
14440
14456
  async (args) => {
14441
14457
  const { id, content, ...updates } = args;
@@ -14586,11 +14602,21 @@ function createActionTools(store) {
14586
14602
  owner: external_exports.string().optional().describe("New owner"),
14587
14603
  priority: external_exports.string().optional().describe("New priority"),
14588
14604
  dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
14605
+ tags: external_exports.array(external_exports.string()).optional().describe("Replace all tags. When provided with sprints, sprint tags are merged into this array."),
14589
14606
  sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001'].")
14590
14607
  },
14591
14608
  async (args) => {
14592
- const { id, content, sprints, ...updates } = args;
14593
- if (sprints !== void 0) {
14609
+ const { id, content, sprints, tags, ...updates } = args;
14610
+ if (tags !== void 0) {
14611
+ const merged = [...tags];
14612
+ if (sprints) {
14613
+ for (const s of sprints) {
14614
+ const tag = `sprint:${s}`;
14615
+ if (!merged.includes(tag)) merged.push(tag);
14616
+ }
14617
+ }
14618
+ updates.tags = merged;
14619
+ } else if (sprints !== void 0) {
14594
14620
  const existing = store.get(id);
14595
14621
  if (!existing) {
14596
14622
  return {
@@ -14737,7 +14763,8 @@ function createQuestionTools(store) {
14737
14763
  title: external_exports.string().optional().describe("New title"),
14738
14764
  status: external_exports.string().optional().describe("New status (e.g. 'answered')"),
14739
14765
  content: external_exports.string().optional().describe("Updated content / answer"),
14740
- owner: external_exports.string().optional().describe("New owner")
14766
+ owner: external_exports.string().optional().describe("New owner"),
14767
+ tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
14741
14768
  },
14742
14769
  async (args) => {
14743
14770
  const { id, content, ...updates } = args;
@@ -14995,7 +15022,7 @@ function collectGarMetrics(store) {
14995
15022
  );
14996
15023
  const openQuestions = store.list({ type: "question", status: "open" });
14997
15024
  const riskItems = allDocs.filter(
14998
- (d) => d.frontmatter.tags?.includes("risk")
15025
+ (d) => d.frontmatter.tags?.includes("risk") && d.frontmatter.status !== "done" && d.frontmatter.status !== "closed"
14999
15026
  );
15000
15027
  const unownedActions = openActions.filter((d) => !d.frontmatter.owner);
15001
15028
  const total = allActions.length;
@@ -16978,7 +17005,7 @@ function createReportTools(store) {
16978
17005
  async () => {
16979
17006
  const allDocs = store.list();
16980
17007
  const taggedRisks = allDocs.filter(
16981
- (d) => d.frontmatter.tags?.includes("risk")
17008
+ (d) => d.frontmatter.tags?.includes("risk") && d.frontmatter.status !== "done" && d.frontmatter.status !== "closed"
16982
17009
  );
16983
17010
  const highPriorityActions = store.list({ type: "action", status: "open" }).filter((d) => d.frontmatter.priority === "high");
16984
17011
  const unresolvedQuestions = store.list({ type: "question", status: "open" });
@@ -17349,7 +17376,8 @@ function createFeatureTools(store) {
17349
17376
  status: external_exports.enum(["draft", "approved", "deferred", "done"]).optional().describe("New status"),
17350
17377
  content: external_exports.string().optional().describe("New content"),
17351
17378
  owner: external_exports.string().optional().describe("New owner"),
17352
- priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority")
17379
+ priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
17380
+ tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
17353
17381
  },
17354
17382
  async (args) => {
17355
17383
  const { id, content, ...updates } = args;
@@ -17506,7 +17534,8 @@ function createEpicTools(store) {
17506
17534
  content: external_exports.string().optional().describe("New content"),
17507
17535
  owner: external_exports.string().optional().describe("New owner"),
17508
17536
  targetDate: external_exports.string().optional().describe("New target date"),
17509
- estimatedEffort: external_exports.string().optional().describe("New estimated effort")
17537
+ estimatedEffort: external_exports.string().optional().describe("New estimated effort"),
17538
+ tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
17510
17539
  },
17511
17540
  async (args) => {
17512
17541
  const { id, content, ...updates } = args;
@@ -18787,8 +18816,8 @@ function createAemReportTools(store) {
18787
18816
  }
18788
18817
 
18789
18818
  // src/plugins/builtin/tools/aem-phase.ts
18790
- import * as fs4 from "fs";
18791
- import * as path4 from "path";
18819
+ import * as fs5 from "fs";
18820
+ import * as path5 from "path";
18792
18821
  import * as YAML2 from "yaml";
18793
18822
  import { tool as tool18 } from "@anthropic-ai/claude-agent-sdk";
18794
18823
  var PHASES = ["assess-use-case", "assess-technology", "define-solution"];
@@ -18893,8 +18922,8 @@ function createAemPhaseTools(store, marvinDir) {
18893
18922
  function readPhase(marvinDir) {
18894
18923
  if (!marvinDir) return void 0;
18895
18924
  try {
18896
- const configPath = path4.join(marvinDir, "config.yaml");
18897
- const raw = fs4.readFileSync(configPath, "utf-8");
18925
+ const configPath = path5.join(marvinDir, "config.yaml");
18926
+ const raw = fs5.readFileSync(configPath, "utf-8");
18898
18927
  const config2 = YAML2.parse(raw);
18899
18928
  const aem = config2.aem;
18900
18929
  return aem?.currentPhase;
@@ -18904,14 +18933,14 @@ function readPhase(marvinDir) {
18904
18933
  }
18905
18934
  function writePhase(marvinDir, phase) {
18906
18935
  if (!marvinDir) return;
18907
- const configPath = path4.join(marvinDir, "config.yaml");
18908
- const raw = fs4.readFileSync(configPath, "utf-8");
18936
+ const configPath = path5.join(marvinDir, "config.yaml");
18937
+ const raw = fs5.readFileSync(configPath, "utf-8");
18909
18938
  const config2 = YAML2.parse(raw);
18910
18939
  if (!config2.aem) {
18911
18940
  config2.aem = {};
18912
18941
  }
18913
18942
  config2.aem.currentPhase = phase;
18914
- fs4.writeFileSync(configPath, YAML2.stringify(config2), "utf-8");
18943
+ fs5.writeFileSync(configPath, YAML2.stringify(config2), "utf-8");
18915
18944
  }
18916
18945
  function getPhaseDescription(phase) {
18917
18946
  switch (phase) {
@@ -19079,8 +19108,8 @@ function getPluginPromptFragment(plugin, personaId) {
19079
19108
  }
19080
19109
 
19081
19110
  // src/skills/registry.ts
19082
- import * as fs5 from "fs";
19083
- import * as path5 from "path";
19111
+ import * as fs6 from "fs";
19112
+ import * as path6 from "path";
19084
19113
  import { fileURLToPath } from "url";
19085
19114
  import * as YAML3 from "yaml";
19086
19115
  import matter2 from "gray-matter";
@@ -19133,8 +19162,8 @@ var JiraClient = class {
19133
19162
  this.baseUrl = `https://${config2.host}/rest/api/2`;
19134
19163
  this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
19135
19164
  }
19136
- async request(path18, method = "GET", body) {
19137
- const url2 = `${this.baseUrl}${path18}`;
19165
+ async request(path20, method = "GET", body) {
19166
+ const url2 = `${this.baseUrl}${path20}`;
19138
19167
  const headers = {
19139
19168
  Authorization: this.authHeader,
19140
19169
  "Content-Type": "application/json",
@@ -19148,7 +19177,7 @@ var JiraClient = class {
19148
19177
  if (!response.ok) {
19149
19178
  const text = await response.text().catch(() => "");
19150
19179
  throw new Error(
19151
- `Jira API error ${response.status} ${method} ${path18}: ${text}`
19180
+ `Jira API error ${response.status} ${method} ${path20}: ${text}`
19152
19181
  );
19153
19182
  }
19154
19183
  if (response.status === 204) return void 0;
@@ -19628,13 +19657,13 @@ var GOVERNANCE_TOOL_NAMES = [
19628
19657
  ];
19629
19658
  function getBuiltinSkillsDir() {
19630
19659
  const thisFile = fileURLToPath(import.meta.url);
19631
- return path5.join(path5.dirname(thisFile), "builtin");
19660
+ return path6.join(path6.dirname(thisFile), "builtin");
19632
19661
  }
19633
19662
  function loadSkillFromDirectory(dirPath) {
19634
- const skillMdPath = path5.join(dirPath, "SKILL.md");
19635
- if (!fs5.existsSync(skillMdPath)) return void 0;
19663
+ const skillMdPath = path6.join(dirPath, "SKILL.md");
19664
+ if (!fs6.existsSync(skillMdPath)) return void 0;
19636
19665
  try {
19637
- const raw = fs5.readFileSync(skillMdPath, "utf-8");
19666
+ const raw = fs6.readFileSync(skillMdPath, "utf-8");
19638
19667
  const { data, content } = matter2(raw);
19639
19668
  if (!data.name || !data.description) return void 0;
19640
19669
  const metadata = data.metadata ?? {};
@@ -19645,13 +19674,13 @@ function loadSkillFromDirectory(dirPath) {
19645
19674
  if (wildcardPrompt) {
19646
19675
  promptFragments["*"] = wildcardPrompt;
19647
19676
  }
19648
- const personasDir = path5.join(dirPath, "personas");
19649
- if (fs5.existsSync(personasDir)) {
19677
+ const personasDir = path6.join(dirPath, "personas");
19678
+ if (fs6.existsSync(personasDir)) {
19650
19679
  try {
19651
- for (const file2 of fs5.readdirSync(personasDir)) {
19680
+ for (const file2 of fs6.readdirSync(personasDir)) {
19652
19681
  if (!file2.endsWith(".md")) continue;
19653
19682
  const personaId = file2.replace(/\.md$/, "");
19654
- const personaPrompt = fs5.readFileSync(path5.join(personasDir, file2), "utf-8").trim();
19683
+ const personaPrompt = fs6.readFileSync(path6.join(personasDir, file2), "utf-8").trim();
19655
19684
  if (personaPrompt) {
19656
19685
  promptFragments[personaId] = personaPrompt;
19657
19686
  }
@@ -19660,10 +19689,10 @@ function loadSkillFromDirectory(dirPath) {
19660
19689
  }
19661
19690
  }
19662
19691
  let actions;
19663
- const actionsPath = path5.join(dirPath, "actions.yaml");
19664
- if (fs5.existsSync(actionsPath)) {
19692
+ const actionsPath = path6.join(dirPath, "actions.yaml");
19693
+ if (fs6.existsSync(actionsPath)) {
19665
19694
  try {
19666
- const actionsRaw = fs5.readFileSync(actionsPath, "utf-8");
19695
+ const actionsRaw = fs6.readFileSync(actionsPath, "utf-8");
19667
19696
  actions = YAML3.parse(actionsRaw);
19668
19697
  } catch {
19669
19698
  }
@@ -19690,10 +19719,10 @@ function loadAllSkills(marvinDir) {
19690
19719
  }
19691
19720
  try {
19692
19721
  const builtinDir = getBuiltinSkillsDir();
19693
- if (fs5.existsSync(builtinDir)) {
19694
- for (const entry of fs5.readdirSync(builtinDir)) {
19695
- const entryPath = path5.join(builtinDir, entry);
19696
- if (!fs5.statSync(entryPath).isDirectory()) continue;
19722
+ if (fs6.existsSync(builtinDir)) {
19723
+ for (const entry of fs6.readdirSync(builtinDir)) {
19724
+ const entryPath = path6.join(builtinDir, entry);
19725
+ if (!fs6.statSync(entryPath).isDirectory()) continue;
19697
19726
  if (skills.has(entry)) continue;
19698
19727
  const skill = loadSkillFromDirectory(entryPath);
19699
19728
  if (skill) skills.set(skill.id, skill);
@@ -19702,18 +19731,18 @@ function loadAllSkills(marvinDir) {
19702
19731
  } catch {
19703
19732
  }
19704
19733
  if (marvinDir) {
19705
- const skillsDir = path5.join(marvinDir, "skills");
19706
- if (fs5.existsSync(skillsDir)) {
19734
+ const skillsDir = path6.join(marvinDir, "skills");
19735
+ if (fs6.existsSync(skillsDir)) {
19707
19736
  let entries;
19708
19737
  try {
19709
- entries = fs5.readdirSync(skillsDir);
19738
+ entries = fs6.readdirSync(skillsDir);
19710
19739
  } catch {
19711
19740
  entries = [];
19712
19741
  }
19713
19742
  for (const entry of entries) {
19714
- const entryPath = path5.join(skillsDir, entry);
19743
+ const entryPath = path6.join(skillsDir, entry);
19715
19744
  try {
19716
- if (fs5.statSync(entryPath).isDirectory()) {
19745
+ if (fs6.statSync(entryPath).isDirectory()) {
19717
19746
  const skill = loadSkillFromDirectory(entryPath);
19718
19747
  if (skill) skills.set(skill.id, skill);
19719
19748
  continue;
@@ -19723,7 +19752,7 @@ function loadAllSkills(marvinDir) {
19723
19752
  }
19724
19753
  if (!entry.endsWith(".yaml") && !entry.endsWith(".yml")) continue;
19725
19754
  try {
19726
- const raw = fs5.readFileSync(entryPath, "utf-8");
19755
+ const raw = fs6.readFileSync(entryPath, "utf-8");
19727
19756
  const parsed = YAML3.parse(raw);
19728
19757
  if (!parsed?.id || !parsed?.name || !parsed?.version) continue;
19729
19758
  const skill = {
@@ -19828,12 +19857,12 @@ function getSkillAgentDefinitions(skillIds, allSkills) {
19828
19857
  return agents;
19829
19858
  }
19830
19859
  function migrateYamlToSkillMd(yamlPath, outputDir) {
19831
- const raw = fs5.readFileSync(yamlPath, "utf-8");
19860
+ const raw = fs6.readFileSync(yamlPath, "utf-8");
19832
19861
  const parsed = YAML3.parse(raw);
19833
19862
  if (!parsed?.id || !parsed?.name) {
19834
19863
  throw new Error(`Invalid skill YAML: missing required fields (id, name)`);
19835
19864
  }
19836
- fs5.mkdirSync(outputDir, { recursive: true });
19865
+ fs6.mkdirSync(outputDir, { recursive: true });
19837
19866
  const frontmatter = {
19838
19867
  name: parsed.id,
19839
19868
  description: parsed.description ?? ""
@@ -19847,15 +19876,15 @@ function migrateYamlToSkillMd(yamlPath, outputDir) {
19847
19876
  const skillMd = matter2.stringify(wildcardPrompt ? `
19848
19877
  ${wildcardPrompt}
19849
19878
  ` : "\n", frontmatter);
19850
- fs5.writeFileSync(path5.join(outputDir, "SKILL.md"), skillMd, "utf-8");
19879
+ fs6.writeFileSync(path6.join(outputDir, "SKILL.md"), skillMd, "utf-8");
19851
19880
  if (promptFragments) {
19852
19881
  const personaKeys = Object.keys(promptFragments).filter((k) => k !== "*");
19853
19882
  if (personaKeys.length > 0) {
19854
- const personasDir = path5.join(outputDir, "personas");
19855
- fs5.mkdirSync(personasDir, { recursive: true });
19883
+ const personasDir = path6.join(outputDir, "personas");
19884
+ fs6.mkdirSync(personasDir, { recursive: true });
19856
19885
  for (const personaId of personaKeys) {
19857
- fs5.writeFileSync(
19858
- path5.join(personasDir, `${personaId}.md`),
19886
+ fs6.writeFileSync(
19887
+ path6.join(personasDir, `${personaId}.md`),
19859
19888
  `${promptFragments[personaId]}
19860
19889
  `,
19861
19890
  "utf-8"
@@ -19865,8 +19894,8 @@ ${wildcardPrompt}
19865
19894
  }
19866
19895
  const actions = parsed.actions;
19867
19896
  if (actions && actions.length > 0) {
19868
- fs5.writeFileSync(
19869
- path5.join(outputDir, "actions.yaml"),
19897
+ fs6.writeFileSync(
19898
+ path6.join(outputDir, "actions.yaml"),
19870
19899
  YAML3.stringify(actions),
19871
19900
  "utf-8"
19872
19901
  );
@@ -20109,8 +20138,8 @@ function createMarvinMcpServer(store, options) {
20109
20138
  }
20110
20139
 
20111
20140
  // src/agent/session.ts
20112
- import * as fs8 from "fs";
20113
- import * as path8 from "path";
20141
+ import * as fs9 from "fs";
20142
+ import * as path9 from "path";
20114
20143
  import * as readline from "readline";
20115
20144
  import chalk from "chalk";
20116
20145
  import ora from "ora";
@@ -20119,13 +20148,13 @@ import {
20119
20148
  } from "@anthropic-ai/claude-agent-sdk";
20120
20149
 
20121
20150
  // src/storage/session-store.ts
20122
- import * as fs6 from "fs";
20123
- import * as path6 from "path";
20151
+ import * as fs7 from "fs";
20152
+ import * as path7 from "path";
20124
20153
  import * as YAML4 from "yaml";
20125
20154
  var SessionStore = class {
20126
20155
  filePath;
20127
20156
  constructor(marvinDir) {
20128
- this.filePath = path6.join(marvinDir, "sessions.yaml");
20157
+ this.filePath = path7.join(marvinDir, "sessions.yaml");
20129
20158
  }
20130
20159
  list() {
20131
20160
  const entries = this.load();
@@ -20166,9 +20195,9 @@ var SessionStore = class {
20166
20195
  this.write(entries);
20167
20196
  }
20168
20197
  load() {
20169
- if (!fs6.existsSync(this.filePath)) return [];
20198
+ if (!fs7.existsSync(this.filePath)) return [];
20170
20199
  try {
20171
- const raw = fs6.readFileSync(this.filePath, "utf-8");
20200
+ const raw = fs7.readFileSync(this.filePath, "utf-8");
20172
20201
  const parsed = YAML4.parse(raw);
20173
20202
  if (!Array.isArray(parsed)) return [];
20174
20203
  return parsed;
@@ -20177,11 +20206,11 @@ var SessionStore = class {
20177
20206
  }
20178
20207
  }
20179
20208
  write(entries) {
20180
- const dir = path6.dirname(this.filePath);
20181
- if (!fs6.existsSync(dir)) {
20182
- fs6.mkdirSync(dir, { recursive: true });
20209
+ const dir = path7.dirname(this.filePath);
20210
+ if (!fs7.existsSync(dir)) {
20211
+ fs7.mkdirSync(dir, { recursive: true });
20183
20212
  }
20184
- fs6.writeFileSync(this.filePath, YAML4.stringify(entries), "utf-8");
20213
+ fs7.writeFileSync(this.filePath, YAML4.stringify(entries), "utf-8");
20185
20214
  }
20186
20215
  };
20187
20216
 
@@ -20217,8 +20246,8 @@ function slugify3(text) {
20217
20246
  }
20218
20247
 
20219
20248
  // src/sources/manifest.ts
20220
- import * as fs7 from "fs";
20221
- import * as path7 from "path";
20249
+ import * as fs8 from "fs";
20250
+ import * as path8 from "path";
20222
20251
  import * as crypto from "crypto";
20223
20252
  import * as YAML5 from "yaml";
20224
20253
  var MANIFEST_FILE = ".manifest.yaml";
@@ -20231,37 +20260,37 @@ var SourceManifestManager = class {
20231
20260
  manifestPath;
20232
20261
  sourcesDir;
20233
20262
  constructor(marvinDir) {
20234
- this.sourcesDir = path7.join(marvinDir, "sources");
20235
- this.manifestPath = path7.join(this.sourcesDir, MANIFEST_FILE);
20263
+ this.sourcesDir = path8.join(marvinDir, "sources");
20264
+ this.manifestPath = path8.join(this.sourcesDir, MANIFEST_FILE);
20236
20265
  this.manifest = this.load();
20237
20266
  }
20238
20267
  load() {
20239
- if (!fs7.existsSync(this.manifestPath)) {
20268
+ if (!fs8.existsSync(this.manifestPath)) {
20240
20269
  return emptyManifest();
20241
20270
  }
20242
- const raw = fs7.readFileSync(this.manifestPath, "utf-8");
20271
+ const raw = fs8.readFileSync(this.manifestPath, "utf-8");
20243
20272
  const parsed = YAML5.parse(raw);
20244
20273
  return parsed ?? emptyManifest();
20245
20274
  }
20246
20275
  save() {
20247
- fs7.mkdirSync(this.sourcesDir, { recursive: true });
20248
- fs7.writeFileSync(this.manifestPath, YAML5.stringify(this.manifest), "utf-8");
20276
+ fs8.mkdirSync(this.sourcesDir, { recursive: true });
20277
+ fs8.writeFileSync(this.manifestPath, YAML5.stringify(this.manifest), "utf-8");
20249
20278
  }
20250
20279
  scan() {
20251
20280
  const added = [];
20252
20281
  const changed = [];
20253
20282
  const removed = [];
20254
- if (!fs7.existsSync(this.sourcesDir)) {
20283
+ if (!fs8.existsSync(this.sourcesDir)) {
20255
20284
  return { added, changed, removed };
20256
20285
  }
20257
20286
  const onDisk = new Set(
20258
- fs7.readdirSync(this.sourcesDir).filter((f) => {
20259
- const ext = path7.extname(f).toLowerCase();
20287
+ fs8.readdirSync(this.sourcesDir).filter((f) => {
20288
+ const ext = path8.extname(f).toLowerCase();
20260
20289
  return SOURCE_EXTENSIONS.includes(ext);
20261
20290
  })
20262
20291
  );
20263
20292
  for (const fileName of onDisk) {
20264
- const filePath = path7.join(this.sourcesDir, fileName);
20293
+ const filePath = path8.join(this.sourcesDir, fileName);
20265
20294
  const hash2 = this.hashFile(filePath);
20266
20295
  const existing = this.manifest.files[fileName];
20267
20296
  if (!existing) {
@@ -20324,7 +20353,7 @@ var SourceManifestManager = class {
20324
20353
  this.save();
20325
20354
  }
20326
20355
  hashFile(filePath) {
20327
- const content = fs7.readFileSync(filePath);
20356
+ const content = fs8.readFileSync(filePath);
20328
20357
  return crypto.createHash("sha256").update(content).digest("hex");
20329
20358
  }
20330
20359
  };
@@ -20339,8 +20368,8 @@ async function startSession(options) {
20339
20368
  const skillRegistrations = collectSkillRegistrations(skillIds, allSkills);
20340
20369
  const store = new DocumentStore(marvinDir, [...pluginRegistrations, ...skillRegistrations]);
20341
20370
  const sessionStore = new SessionStore(marvinDir);
20342
- const sourcesDir = path8.join(marvinDir, "sources");
20343
- const hasSourcesDir = fs8.existsSync(sourcesDir);
20371
+ const sourcesDir = path9.join(marvinDir, "sources");
20372
+ const hasSourcesDir = fs9.existsSync(sourcesDir);
20344
20373
  const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
20345
20374
  const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
20346
20375
  const pluginPromptFragment = plugin ? getPluginPromptFragment(plugin, persona.id) : void 0;
@@ -20363,7 +20392,7 @@ async function startSession(options) {
20363
20392
  projectName: config2.project.name,
20364
20393
  navGroups
20365
20394
  });
20366
- const systemPrompt = buildSystemPrompt(persona, config2.project, pluginPromptFragment, skillPromptFragment);
20395
+ const systemPrompt = buildSystemPrompt(persona, config2.project, pluginPromptFragment, skillPromptFragment, marvinDir);
20367
20396
  let existingSession;
20368
20397
  if (options.sessionName) {
20369
20398
  existingSession = sessionStore.get(options.sessionName);
@@ -20549,8 +20578,8 @@ Session ended with error: ${message.subtype}`));
20549
20578
  }
20550
20579
 
20551
20580
  // src/mcp/stdio-server.ts
20552
- import * as fs9 from "fs";
20553
- import * as path9 from "path";
20581
+ import * as fs10 from "fs";
20582
+ import * as path10 from "path";
20554
20583
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
20555
20584
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20556
20585
 
@@ -20848,8 +20877,8 @@ function collectTools(marvinDir) {
20848
20877
  const plugin = resolvePlugin(config2.methodology);
20849
20878
  const registrations = plugin?.documentTypeRegistrations ?? [];
20850
20879
  const store = new DocumentStore(marvinDir, registrations);
20851
- const sourcesDir = path9.join(marvinDir, "sources");
20852
- const hasSourcesDir = fs9.existsSync(sourcesDir);
20880
+ const sourcesDir = path10.join(marvinDir, "sources");
20881
+ const hasSourcesDir = fs10.existsSync(sourcesDir);
20853
20882
  const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
20854
20883
  const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
20855
20884
  const sessionStore = new SessionStore(marvinDir);
@@ -20857,8 +20886,14 @@ function collectTools(marvinDir) {
20857
20886
  const allSkillIds = [...allSkills.keys()];
20858
20887
  const codeSkillTools = getSkillTools(allSkillIds, allSkills, store);
20859
20888
  const skillsWithActions = allSkillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
20860
- const projectRoot = path9.dirname(marvinDir);
20889
+ const projectRoot = path10.dirname(marvinDir);
20861
20890
  const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
20891
+ const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
20892
+ const navGroups = buildNavGroups({
20893
+ pluginRegs: registrations,
20894
+ skillRegs: allSkillRegs,
20895
+ pluginName: plugin?.name
20896
+ });
20862
20897
  return [
20863
20898
  ...createDecisionTools(store),
20864
20899
  ...createActionTools(store),
@@ -20866,6 +20901,7 @@ function collectTools(marvinDir) {
20866
20901
  ...createDocumentTools(store),
20867
20902
  ...manifest ? createSourceTools(manifest) : [],
20868
20903
  ...createSessionTools(sessionStore),
20904
+ ...createWebTools(store, config2.name, navGroups),
20869
20905
  ...pluginTools,
20870
20906
  ...codeSkillTools,
20871
20907
  ...actionTools
@@ -20917,11 +20953,63 @@ async function startStdioServer(options) {
20917
20953
  import { Command } from "commander";
20918
20954
 
20919
20955
  // src/cli/commands/init.ts
20920
- import * as fs10 from "fs";
20921
- import * as path10 from "path";
20956
+ import * as fs11 from "fs";
20957
+ import * as path11 from "path";
20922
20958
  import * as YAML6 from "yaml";
20923
20959
  import chalk2 from "chalk";
20924
20960
  import { input, confirm, select } from "@inquirer/prompts";
20961
+
20962
+ // src/templates/claude-md.ts
20963
+ function getDefaultClaudeMdContent(projectName) {
20964
+ return `# Marvin \u2014 Project Instructions for "${projectName}"
20965
+
20966
+ You are **Marvin**, an AI-powered product development assistant.
20967
+ You operate as one of three personas \u2014 stay in role and suggest switching when a question falls outside your scope.
20968
+
20969
+ ## Personas
20970
+
20971
+ | Persona | Short | Focus |
20972
+ |---------|-------|-------|
20973
+ | Product Owner | po | Vision, backlog, requirements, features, acceptance criteria |
20974
+ | Delivery Manager | dm | Planning, risks, actions, timelines, sprints, status |
20975
+ | Tech Lead | tl | Architecture, trade-offs, technical decisions, code quality |
20976
+
20977
+ ## Proactive Governance
20978
+
20979
+ When conversation implies a commitment, risk, or open question, **suggest creating the matching artifact**:
20980
+ - A decision was made \u2192 offer to create a **Decision (D-xxx)**
20981
+ - Someone committed to a task \u2192 offer an **Action (A-xxx)** with owner and due date
20982
+ - An unanswered question surfaced \u2192 offer a **Question (Q-xxx)**
20983
+ - A new capability is discussed \u2192 offer a **Feature (F-xxx)**
20984
+ - Implementation scope is agreed \u2192 offer an **Epic (E-xxx)** linked to a feature
20985
+ - Work is being time-boxed \u2192 offer a **Sprint (SP-xxx)**
20986
+
20987
+ ## Insights
20988
+
20989
+ Proactively flag:
20990
+ - Overdue actions or unresolved questions
20991
+ - Decisions without rationale or linked features
20992
+ - Features without linked epics
20993
+ - Risks mentioned but not tracked
20994
+ - When a risk is resolved \u2192 remove the "risk" tag and add "risk-mitigated"
20995
+
20996
+ ## Tool Usage
20997
+
20998
+ - **Search before creating** \u2014 avoid duplicate artifacts
20999
+ - **Reference IDs** (e.g. D-001, A-003) when discussing existing items
21000
+ - **Link artifacts** \u2014 epics to features, actions to decisions, etc.
21001
+ - Use \`search_documents\` to find related context before answering
21002
+
21003
+ ## Communication Style
21004
+
21005
+ - Be concise and structured
21006
+ - State assumptions explicitly
21007
+ - Use bullet points and tables where they aid clarity
21008
+ - When uncertain, ask a clarifying question rather than guessing
21009
+ `;
21010
+ }
21011
+
21012
+ // src/cli/commands/init.ts
20925
21013
  async function initCommand() {
20926
21014
  const cwd = process.cwd();
20927
21015
  if (isMarvinProject(cwd)) {
@@ -20932,7 +21020,7 @@ async function initCommand() {
20932
21020
  }
20933
21021
  const projectName = await input({
20934
21022
  message: "Project name:",
20935
- default: path10.basename(cwd)
21023
+ default: path11.basename(cwd)
20936
21024
  });
20937
21025
  const methodology = await select({
20938
21026
  message: "Methodology:",
@@ -20944,21 +21032,21 @@ async function initCommand() {
20944
21032
  });
20945
21033
  const plugin = resolvePlugin(methodology);
20946
21034
  const registrations = plugin?.documentTypeRegistrations ?? [];
20947
- const marvinDir = path10.join(cwd, ".marvin");
21035
+ const marvinDir = path11.join(cwd, ".marvin");
20948
21036
  const dirs = [
20949
21037
  marvinDir,
20950
- path10.join(marvinDir, "templates"),
20951
- path10.join(marvinDir, "docs", "decisions"),
20952
- path10.join(marvinDir, "docs", "actions"),
20953
- path10.join(marvinDir, "docs", "questions"),
20954
- path10.join(marvinDir, "sources"),
20955
- path10.join(marvinDir, "skills")
21038
+ path11.join(marvinDir, "templates"),
21039
+ path11.join(marvinDir, "docs", "decisions"),
21040
+ path11.join(marvinDir, "docs", "actions"),
21041
+ path11.join(marvinDir, "docs", "questions"),
21042
+ path11.join(marvinDir, "sources"),
21043
+ path11.join(marvinDir, "skills")
20956
21044
  ];
20957
21045
  for (const reg of registrations) {
20958
- dirs.push(path10.join(marvinDir, "docs", reg.dirName));
21046
+ dirs.push(path11.join(marvinDir, "docs", reg.dirName));
20959
21047
  }
20960
21048
  for (const dir of dirs) {
20961
- fs10.mkdirSync(dir, { recursive: true });
21049
+ fs11.mkdirSync(dir, { recursive: true });
20962
21050
  }
20963
21051
  const config2 = {
20964
21052
  name: projectName,
@@ -20972,16 +21060,22 @@ async function initCommand() {
20972
21060
  if (methodology === "sap-aem") {
20973
21061
  config2.aem = { currentPhase: "assess-use-case" };
20974
21062
  }
20975
- fs10.writeFileSync(
20976
- path10.join(marvinDir, "config.yaml"),
21063
+ fs11.writeFileSync(
21064
+ path11.join(marvinDir, "config.yaml"),
20977
21065
  YAML6.stringify(config2),
20978
21066
  "utf-8"
20979
21067
  );
21068
+ fs11.writeFileSync(
21069
+ path11.join(marvinDir, "CLAUDE.md"),
21070
+ getDefaultClaudeMdContent(projectName),
21071
+ "utf-8"
21072
+ );
20980
21073
  console.log(chalk2.green(`
20981
21074
  Initialized Marvin project "${projectName}" in ${cwd}`));
20982
21075
  console.log(chalk2.dim(`Methodology: ${plugin?.name ?? methodology}`));
20983
21076
  console.log(chalk2.dim("\nCreated:"));
20984
21077
  console.log(chalk2.dim(" .marvin/config.yaml"));
21078
+ console.log(chalk2.dim(" .marvin/CLAUDE.md"));
20985
21079
  console.log(chalk2.dim(" .marvin/docs/decisions/"));
20986
21080
  console.log(chalk2.dim(" .marvin/docs/actions/"));
20987
21081
  console.log(chalk2.dim(" .marvin/docs/questions/"));
@@ -20999,18 +21093,18 @@ Initialized Marvin project "${projectName}" in ${cwd}`));
20999
21093
  const sourceDir = await input({
21000
21094
  message: "Path to directory containing source documents:"
21001
21095
  });
21002
- const resolvedDir = path10.resolve(sourceDir);
21003
- if (fs10.existsSync(resolvedDir) && fs10.statSync(resolvedDir).isDirectory()) {
21096
+ const resolvedDir = path11.resolve(sourceDir);
21097
+ if (fs11.existsSync(resolvedDir) && fs11.statSync(resolvedDir).isDirectory()) {
21004
21098
  const sourceExts = [".pdf", ".md", ".txt"];
21005
- const files = fs10.readdirSync(resolvedDir).filter((f) => {
21006
- const ext = path10.extname(f).toLowerCase();
21099
+ const files = fs11.readdirSync(resolvedDir).filter((f) => {
21100
+ const ext = path11.extname(f).toLowerCase();
21007
21101
  return sourceExts.includes(ext);
21008
21102
  });
21009
21103
  let copied = 0;
21010
21104
  for (const file2 of files) {
21011
- const src = path10.join(resolvedDir, file2);
21012
- const dest = path10.join(marvinDir, "sources", file2);
21013
- fs10.copyFileSync(src, dest);
21105
+ const src = path11.join(resolvedDir, file2);
21106
+ const dest = path11.join(marvinDir, "sources", file2);
21107
+ fs11.copyFileSync(src, dest);
21014
21108
  copied++;
21015
21109
  }
21016
21110
  if (copied > 0) {
@@ -21300,13 +21394,13 @@ async function setApiKey() {
21300
21394
  }
21301
21395
 
21302
21396
  // src/cli/commands/ingest.ts
21303
- import * as fs12 from "fs";
21304
- import * as path12 from "path";
21397
+ import * as fs13 from "fs";
21398
+ import * as path13 from "path";
21305
21399
  import chalk8 from "chalk";
21306
21400
 
21307
21401
  // src/sources/ingest.ts
21308
- import * as fs11 from "fs";
21309
- import * as path11 from "path";
21402
+ import * as fs12 from "fs";
21403
+ import * as path12 from "path";
21310
21404
  import chalk7 from "chalk";
21311
21405
  import ora2 from "ora";
21312
21406
  import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
@@ -21409,15 +21503,15 @@ async function ingestFile(options) {
21409
21503
  const persona = getPersona(personaId);
21410
21504
  const manifest = new SourceManifestManager(marvinDir);
21411
21505
  const sourcesDir = manifest.sourcesDir;
21412
- const filePath = path11.join(sourcesDir, fileName);
21413
- if (!fs11.existsSync(filePath)) {
21506
+ const filePath = path12.join(sourcesDir, fileName);
21507
+ if (!fs12.existsSync(filePath)) {
21414
21508
  throw new Error(`Source file not found: ${filePath}`);
21415
21509
  }
21416
- const ext = path11.extname(fileName).toLowerCase();
21510
+ const ext = path12.extname(fileName).toLowerCase();
21417
21511
  const isPdf = ext === ".pdf";
21418
21512
  let fileContent = null;
21419
21513
  if (!isPdf) {
21420
- fileContent = fs11.readFileSync(filePath, "utf-8");
21514
+ fileContent = fs12.readFileSync(filePath, "utf-8");
21421
21515
  }
21422
21516
  const store = new DocumentStore(marvinDir);
21423
21517
  const createdArtifacts = [];
@@ -21520,9 +21614,9 @@ Ingest ended with error: ${message.subtype}`)
21520
21614
  async function ingestCommand(file2, options) {
21521
21615
  const project = loadProject();
21522
21616
  const marvinDir = project.marvinDir;
21523
- const sourcesDir = path12.join(marvinDir, "sources");
21524
- if (!fs12.existsSync(sourcesDir)) {
21525
- fs12.mkdirSync(sourcesDir, { recursive: true });
21617
+ const sourcesDir = path13.join(marvinDir, "sources");
21618
+ if (!fs13.existsSync(sourcesDir)) {
21619
+ fs13.mkdirSync(sourcesDir, { recursive: true });
21526
21620
  }
21527
21621
  const manifest = new SourceManifestManager(marvinDir);
21528
21622
  manifest.scan();
@@ -21533,8 +21627,8 @@ async function ingestCommand(file2, options) {
21533
21627
  return;
21534
21628
  }
21535
21629
  if (file2) {
21536
- const filePath = path12.join(sourcesDir, file2);
21537
- if (!fs12.existsSync(filePath)) {
21630
+ const filePath = path13.join(sourcesDir, file2);
21631
+ if (!fs13.existsSync(filePath)) {
21538
21632
  console.log(chalk8.red(`Source file not found: ${file2}`));
21539
21633
  console.log(chalk8.dim(`Expected at: ${filePath}`));
21540
21634
  console.log(chalk8.dim(`Drop files into .marvin/sources/ and try again.`));
@@ -21601,7 +21695,7 @@ import ora3 from "ora";
21601
21695
  import { input as input3 } from "@inquirer/prompts";
21602
21696
 
21603
21697
  // src/git/repository.ts
21604
- import * as path13 from "path";
21698
+ import * as path14 from "path";
21605
21699
  import simpleGit from "simple-git";
21606
21700
  var MARVIN_GITIGNORE = `node_modules/
21607
21701
  .DS_Store
@@ -21621,7 +21715,7 @@ var DIR_TYPE_LABELS = {
21621
21715
  function buildCommitMessage(files) {
21622
21716
  const counts = /* @__PURE__ */ new Map();
21623
21717
  for (const f of files) {
21624
- const parts2 = f.split(path13.sep).join("/").split("/");
21718
+ const parts2 = f.split(path14.sep).join("/").split("/");
21625
21719
  const docsIdx = parts2.indexOf("docs");
21626
21720
  if (docsIdx !== -1 && docsIdx + 1 < parts2.length) {
21627
21721
  const dirName = parts2[docsIdx + 1];
@@ -21661,9 +21755,9 @@ var MarvinGit = class {
21661
21755
  );
21662
21756
  }
21663
21757
  await this.git.init();
21664
- const { writeFileSync: writeFileSync9 } = await import("fs");
21665
- writeFileSync9(
21666
- path13.join(this.marvinDir, ".gitignore"),
21758
+ const { writeFileSync: writeFileSync10 } = await import("fs");
21759
+ writeFileSync10(
21760
+ path14.join(this.marvinDir, ".gitignore"),
21667
21761
  MARVIN_GITIGNORE,
21668
21762
  "utf-8"
21669
21763
  );
@@ -21783,9 +21877,9 @@ var MarvinGit = class {
21783
21877
  }
21784
21878
  }
21785
21879
  static async clone(url2, targetDir) {
21786
- const marvinDir = path13.join(targetDir, ".marvin");
21787
- const { existsSync: existsSync16 } = await import("fs");
21788
- if (existsSync16(marvinDir)) {
21880
+ const marvinDir = path14.join(targetDir, ".marvin");
21881
+ const { existsSync: existsSync17 } = await import("fs");
21882
+ if (existsSync17(marvinDir)) {
21789
21883
  throw new GitSyncError(
21790
21884
  `.marvin/ already exists at ${targetDir}. Remove it first or choose a different directory.`
21791
21885
  );
@@ -21969,8 +22063,8 @@ async function serveCommand() {
21969
22063
  }
21970
22064
 
21971
22065
  // src/cli/commands/skills.ts
21972
- import * as fs13 from "fs";
21973
- import * as path14 from "path";
22066
+ import * as fs14 from "fs";
22067
+ import * as path15 from "path";
21974
22068
  import * as YAML7 from "yaml";
21975
22069
  import matter3 from "gray-matter";
21976
22070
  import chalk10 from "chalk";
@@ -22076,14 +22170,14 @@ async function skillsRemoveCommand(skillId, options) {
22076
22170
  }
22077
22171
  async function skillsCreateCommand(name) {
22078
22172
  const project = loadProject();
22079
- const skillsDir = path14.join(project.marvinDir, "skills");
22080
- fs13.mkdirSync(skillsDir, { recursive: true });
22081
- const skillDir = path14.join(skillsDir, name);
22082
- if (fs13.existsSync(skillDir)) {
22173
+ const skillsDir = path15.join(project.marvinDir, "skills");
22174
+ fs14.mkdirSync(skillsDir, { recursive: true });
22175
+ const skillDir = path15.join(skillsDir, name);
22176
+ if (fs14.existsSync(skillDir)) {
22083
22177
  console.log(chalk10.yellow(`Skill directory already exists: ${skillDir}`));
22084
22178
  return;
22085
22179
  }
22086
- fs13.mkdirSync(skillDir, { recursive: true });
22180
+ fs14.mkdirSync(skillDir, { recursive: true });
22087
22181
  const displayName = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
22088
22182
  const frontmatter = {
22089
22183
  name,
@@ -22097,7 +22191,7 @@ async function skillsCreateCommand(name) {
22097
22191
  You have the **${displayName}** skill.
22098
22192
  `;
22099
22193
  const skillMd = matter3.stringify(body, frontmatter);
22100
- fs13.writeFileSync(path14.join(skillDir, "SKILL.md"), skillMd, "utf-8");
22194
+ fs14.writeFileSync(path15.join(skillDir, "SKILL.md"), skillMd, "utf-8");
22101
22195
  const actions = [
22102
22196
  {
22103
22197
  id: "run",
@@ -22107,7 +22201,7 @@ You have the **${displayName}** skill.
22107
22201
  maxTurns: 5
22108
22202
  }
22109
22203
  ];
22110
- fs13.writeFileSync(path14.join(skillDir, "actions.yaml"), YAML7.stringify(actions), "utf-8");
22204
+ fs14.writeFileSync(path15.join(skillDir, "actions.yaml"), YAML7.stringify(actions), "utf-8");
22111
22205
  console.log(chalk10.green(`Created skill: ${skillDir}/`));
22112
22206
  console.log(chalk10.dim(" SKILL.md \u2014 skill definition and prompt"));
22113
22207
  console.log(chalk10.dim(" actions.yaml \u2014 action definitions"));
@@ -22115,14 +22209,14 @@ You have the **${displayName}** skill.
22115
22209
  }
22116
22210
  async function skillsMigrateCommand() {
22117
22211
  const project = loadProject();
22118
- const skillsDir = path14.join(project.marvinDir, "skills");
22119
- if (!fs13.existsSync(skillsDir)) {
22212
+ const skillsDir = path15.join(project.marvinDir, "skills");
22213
+ if (!fs14.existsSync(skillsDir)) {
22120
22214
  console.log(chalk10.dim("No skills directory found."));
22121
22215
  return;
22122
22216
  }
22123
22217
  let entries;
22124
22218
  try {
22125
- entries = fs13.readdirSync(skillsDir);
22219
+ entries = fs14.readdirSync(skillsDir);
22126
22220
  } catch {
22127
22221
  console.log(chalk10.red("Could not read skills directory."));
22128
22222
  return;
@@ -22134,16 +22228,16 @@ async function skillsMigrateCommand() {
22134
22228
  }
22135
22229
  let migrated = 0;
22136
22230
  for (const file2 of yamlFiles) {
22137
- const yamlPath = path14.join(skillsDir, file2);
22231
+ const yamlPath = path15.join(skillsDir, file2);
22138
22232
  const baseName = file2.replace(/\.(yaml|yml)$/, "");
22139
- const outputDir = path14.join(skillsDir, baseName);
22140
- if (fs13.existsSync(outputDir)) {
22233
+ const outputDir = path15.join(skillsDir, baseName);
22234
+ if (fs14.existsSync(outputDir)) {
22141
22235
  console.log(chalk10.yellow(`Skipping "${file2}" \u2014 directory "${baseName}/" already exists.`));
22142
22236
  continue;
22143
22237
  }
22144
22238
  try {
22145
22239
  migrateYamlToSkillMd(yamlPath, outputDir);
22146
- fs13.renameSync(yamlPath, `${yamlPath}.bak`);
22240
+ fs14.renameSync(yamlPath, `${yamlPath}.bak`);
22147
22241
  console.log(chalk10.green(`Migrated "${file2}" \u2192 "${baseName}/"`));
22148
22242
  migrated++;
22149
22243
  } catch (err) {
@@ -22157,35 +22251,35 @@ ${migrated} skill(s) migrated. Original files renamed to *.bak`));
22157
22251
  }
22158
22252
 
22159
22253
  // src/cli/commands/import.ts
22160
- import * as fs16 from "fs";
22161
- import * as path17 from "path";
22254
+ import * as fs17 from "fs";
22255
+ import * as path18 from "path";
22162
22256
  import chalk11 from "chalk";
22163
22257
 
22164
22258
  // src/import/engine.ts
22165
- import * as fs15 from "fs";
22166
- import * as path16 from "path";
22259
+ import * as fs16 from "fs";
22260
+ import * as path17 from "path";
22167
22261
  import matter5 from "gray-matter";
22168
22262
 
22169
22263
  // src/import/classifier.ts
22170
- import * as fs14 from "fs";
22171
- import * as path15 from "path";
22264
+ import * as fs15 from "fs";
22265
+ import * as path16 from "path";
22172
22266
  import matter4 from "gray-matter";
22173
22267
  var RAW_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".txt"]);
22174
22268
  var CORE_DIR_NAMES = /* @__PURE__ */ new Set(["decisions", "actions", "questions"]);
22175
22269
  var ID_PATTERN = /^[A-Z]+-\d{3,}$/;
22176
22270
  function classifyPath(inputPath, knownTypes, knownDirNames) {
22177
- const resolved = path15.resolve(inputPath);
22178
- const stat = fs14.statSync(resolved);
22271
+ const resolved = path16.resolve(inputPath);
22272
+ const stat = fs15.statSync(resolved);
22179
22273
  if (!stat.isDirectory()) {
22180
22274
  return classifyFile(resolved, knownTypes);
22181
22275
  }
22182
- if (path15.basename(resolved) === ".marvin" || fs14.existsSync(path15.join(resolved, "config.yaml"))) {
22276
+ if (path16.basename(resolved) === ".marvin" || fs15.existsSync(path16.join(resolved, "config.yaml"))) {
22183
22277
  return { type: "marvin-project", inputPath: resolved };
22184
22278
  }
22185
22279
  const allDirNames = /* @__PURE__ */ new Set([...CORE_DIR_NAMES, ...knownDirNames]);
22186
- const entries = fs14.readdirSync(resolved);
22280
+ const entries = fs15.readdirSync(resolved);
22187
22281
  const hasDocSubdirs = entries.some(
22188
- (e) => allDirNames.has(e) && fs14.statSync(path15.join(resolved, e)).isDirectory()
22282
+ (e) => allDirNames.has(e) && fs15.statSync(path16.join(resolved, e)).isDirectory()
22189
22283
  );
22190
22284
  if (hasDocSubdirs) {
22191
22285
  return { type: "docs-directory", inputPath: resolved };
@@ -22194,7 +22288,7 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
22194
22288
  if (mdFiles.length > 0) {
22195
22289
  const hasMarvinDocs = mdFiles.some((f) => {
22196
22290
  try {
22197
- const raw = fs14.readFileSync(path15.join(resolved, f), "utf-8");
22291
+ const raw = fs15.readFileSync(path16.join(resolved, f), "utf-8");
22198
22292
  const { data } = matter4(raw);
22199
22293
  return isValidMarvinDocument(data, knownTypes);
22200
22294
  } catch {
@@ -22208,14 +22302,14 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
22208
22302
  return { type: "raw-source-dir", inputPath: resolved };
22209
22303
  }
22210
22304
  function classifyFile(filePath, knownTypes) {
22211
- const resolved = path15.resolve(filePath);
22212
- const ext = path15.extname(resolved).toLowerCase();
22305
+ const resolved = path16.resolve(filePath);
22306
+ const ext = path16.extname(resolved).toLowerCase();
22213
22307
  if (RAW_SOURCE_EXTENSIONS.has(ext)) {
22214
22308
  return { type: "raw-source-file", inputPath: resolved };
22215
22309
  }
22216
22310
  if (ext === ".md") {
22217
22311
  try {
22218
- const raw = fs14.readFileSync(resolved, "utf-8");
22312
+ const raw = fs15.readFileSync(resolved, "utf-8");
22219
22313
  const { data } = matter4(raw);
22220
22314
  if (isValidMarvinDocument(data, knownTypes)) {
22221
22315
  return { type: "marvin-document", inputPath: resolved };
@@ -22340,9 +22434,9 @@ function executeImportPlan(plan, store, marvinDir, options) {
22340
22434
  continue;
22341
22435
  }
22342
22436
  if (item.action === "copy") {
22343
- const targetDir = path16.dirname(item.targetPath);
22344
- fs15.mkdirSync(targetDir, { recursive: true });
22345
- fs15.copyFileSync(item.sourcePath, item.targetPath);
22437
+ const targetDir = path17.dirname(item.targetPath);
22438
+ fs16.mkdirSync(targetDir, { recursive: true });
22439
+ fs16.copyFileSync(item.sourcePath, item.targetPath);
22346
22440
  copied++;
22347
22441
  continue;
22348
22442
  }
@@ -22378,19 +22472,19 @@ function formatPlanSummary(plan) {
22378
22472
  lines.push(`Documents to import: ${imports.length}`);
22379
22473
  for (const item of imports) {
22380
22474
  const idInfo = item.originalId !== item.newId ? `${item.originalId} \u2192 ${item.newId}` : item.newId ?? item.originalId ?? "";
22381
- lines.push(` ${idInfo} ${path16.basename(item.sourcePath)}`);
22475
+ lines.push(` ${idInfo} ${path17.basename(item.sourcePath)}`);
22382
22476
  }
22383
22477
  }
22384
22478
  if (copies.length > 0) {
22385
22479
  lines.push(`Files to copy to sources/: ${copies.length}`);
22386
22480
  for (const item of copies) {
22387
- lines.push(` ${path16.basename(item.sourcePath)} \u2192 ${path16.basename(item.targetPath)}`);
22481
+ lines.push(` ${path17.basename(item.sourcePath)} \u2192 ${path17.basename(item.targetPath)}`);
22388
22482
  }
22389
22483
  }
22390
22484
  if (skips.length > 0) {
22391
22485
  lines.push(`Skipped (conflict): ${skips.length}`);
22392
22486
  for (const item of skips) {
22393
- lines.push(` ${item.originalId ?? path16.basename(item.sourcePath)} ${item.reason ?? ""}`);
22487
+ lines.push(` ${item.originalId ?? path17.basename(item.sourcePath)} ${item.reason ?? ""}`);
22394
22488
  }
22395
22489
  }
22396
22490
  if (plan.items.length === 0) {
@@ -22423,11 +22517,11 @@ function getDirNameForType(store, type) {
22423
22517
  }
22424
22518
  function collectMarvinDocs(dir, knownTypes) {
22425
22519
  const docs = [];
22426
- const files = fs15.readdirSync(dir).filter((f) => f.endsWith(".md"));
22520
+ const files = fs16.readdirSync(dir).filter((f) => f.endsWith(".md"));
22427
22521
  for (const file2 of files) {
22428
- const filePath = path16.join(dir, file2);
22522
+ const filePath = path17.join(dir, file2);
22429
22523
  try {
22430
- const raw = fs15.readFileSync(filePath, "utf-8");
22524
+ const raw = fs16.readFileSync(filePath, "utf-8");
22431
22525
  const { data, content } = matter5(raw);
22432
22526
  if (isValidMarvinDocument(data, knownTypes)) {
22433
22527
  docs.push({
@@ -22483,23 +22577,23 @@ function planDocImports(docs, store, options) {
22483
22577
  }
22484
22578
  function planFromMarvinProject(classification, store, _marvinDir, options) {
22485
22579
  let projectDir = classification.inputPath;
22486
- if (path16.basename(projectDir) !== ".marvin") {
22487
- const inner = path16.join(projectDir, ".marvin");
22488
- if (fs15.existsSync(inner)) {
22580
+ if (path17.basename(projectDir) !== ".marvin") {
22581
+ const inner = path17.join(projectDir, ".marvin");
22582
+ if (fs16.existsSync(inner)) {
22489
22583
  projectDir = inner;
22490
22584
  }
22491
22585
  }
22492
- const docsDir = path16.join(projectDir, "docs");
22493
- if (!fs15.existsSync(docsDir)) {
22586
+ const docsDir = path17.join(projectDir, "docs");
22587
+ if (!fs16.existsSync(docsDir)) {
22494
22588
  return [];
22495
22589
  }
22496
22590
  const knownTypes = store.registeredTypes;
22497
22591
  const allDocs = [];
22498
- const subdirs = fs15.readdirSync(docsDir).filter(
22499
- (d) => fs15.statSync(path16.join(docsDir, d)).isDirectory()
22592
+ const subdirs = fs16.readdirSync(docsDir).filter(
22593
+ (d) => fs16.statSync(path17.join(docsDir, d)).isDirectory()
22500
22594
  );
22501
22595
  for (const subdir of subdirs) {
22502
- const docs = collectMarvinDocs(path16.join(docsDir, subdir), knownTypes);
22596
+ const docs = collectMarvinDocs(path17.join(docsDir, subdir), knownTypes);
22503
22597
  allDocs.push(...docs);
22504
22598
  }
22505
22599
  return planDocImports(allDocs, store, options);
@@ -22509,10 +22603,10 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
22509
22603
  const knownTypes = store.registeredTypes;
22510
22604
  const allDocs = [];
22511
22605
  allDocs.push(...collectMarvinDocs(dir, knownTypes));
22512
- const entries = fs15.readdirSync(dir);
22606
+ const entries = fs16.readdirSync(dir);
22513
22607
  for (const entry of entries) {
22514
- const entryPath = path16.join(dir, entry);
22515
- if (fs15.statSync(entryPath).isDirectory()) {
22608
+ const entryPath = path17.join(dir, entry);
22609
+ if (fs16.statSync(entryPath).isDirectory()) {
22516
22610
  allDocs.push(...collectMarvinDocs(entryPath, knownTypes));
22517
22611
  }
22518
22612
  }
@@ -22521,7 +22615,7 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
22521
22615
  function planFromSingleDocument(classification, store, _marvinDir, options) {
22522
22616
  const filePath = classification.inputPath;
22523
22617
  const knownTypes = store.registeredTypes;
22524
- const raw = fs15.readFileSync(filePath, "utf-8");
22618
+ const raw = fs16.readFileSync(filePath, "utf-8");
22525
22619
  const { data, content } = matter5(raw);
22526
22620
  if (!isValidMarvinDocument(data, knownTypes)) {
22527
22621
  return [];
@@ -22537,14 +22631,14 @@ function planFromSingleDocument(classification, store, _marvinDir, options) {
22537
22631
  }
22538
22632
  function planFromRawSourceDir(classification, marvinDir) {
22539
22633
  const dir = classification.inputPath;
22540
- const sourcesDir = path16.join(marvinDir, "sources");
22634
+ const sourcesDir = path17.join(marvinDir, "sources");
22541
22635
  const items = [];
22542
- const files = fs15.readdirSync(dir).filter((f) => {
22543
- const stat = fs15.statSync(path16.join(dir, f));
22636
+ const files = fs16.readdirSync(dir).filter((f) => {
22637
+ const stat = fs16.statSync(path17.join(dir, f));
22544
22638
  return stat.isFile();
22545
22639
  });
22546
22640
  for (const file2 of files) {
22547
- const sourcePath = path16.join(dir, file2);
22641
+ const sourcePath = path17.join(dir, file2);
22548
22642
  const targetPath = resolveSourceFileName(sourcesDir, file2);
22549
22643
  items.push({
22550
22644
  action: "copy",
@@ -22555,8 +22649,8 @@ function planFromRawSourceDir(classification, marvinDir) {
22555
22649
  return items;
22556
22650
  }
22557
22651
  function planFromRawSourceFile(classification, marvinDir) {
22558
- const sourcesDir = path16.join(marvinDir, "sources");
22559
- const fileName = path16.basename(classification.inputPath);
22652
+ const sourcesDir = path17.join(marvinDir, "sources");
22653
+ const fileName = path17.basename(classification.inputPath);
22560
22654
  const targetPath = resolveSourceFileName(sourcesDir, fileName);
22561
22655
  return [
22562
22656
  {
@@ -22567,25 +22661,25 @@ function planFromRawSourceFile(classification, marvinDir) {
22567
22661
  ];
22568
22662
  }
22569
22663
  function resolveSourceFileName(sourcesDir, fileName) {
22570
- const targetPath = path16.join(sourcesDir, fileName);
22571
- if (!fs15.existsSync(targetPath)) {
22664
+ const targetPath = path17.join(sourcesDir, fileName);
22665
+ if (!fs16.existsSync(targetPath)) {
22572
22666
  return targetPath;
22573
22667
  }
22574
- const ext = path16.extname(fileName);
22575
- const base = path16.basename(fileName, ext);
22668
+ const ext = path17.extname(fileName);
22669
+ const base = path17.basename(fileName, ext);
22576
22670
  let counter = 1;
22577
22671
  let candidate;
22578
22672
  do {
22579
- candidate = path16.join(sourcesDir, `${base}-${counter}${ext}`);
22673
+ candidate = path17.join(sourcesDir, `${base}-${counter}${ext}`);
22580
22674
  counter++;
22581
- } while (fs15.existsSync(candidate));
22675
+ } while (fs16.existsSync(candidate));
22582
22676
  return candidate;
22583
22677
  }
22584
22678
 
22585
22679
  // src/cli/commands/import.ts
22586
22680
  async function importCommand(inputPath, options) {
22587
- const resolved = path17.resolve(inputPath);
22588
- if (!fs16.existsSync(resolved)) {
22681
+ const resolved = path18.resolve(inputPath);
22682
+ if (!fs17.existsSync(resolved)) {
22589
22683
  throw new ImportError(`Path not found: ${resolved}`);
22590
22684
  }
22591
22685
  const project = loadProject();
@@ -22637,7 +22731,7 @@ async function importCommand(inputPath, options) {
22637
22731
  console.log(chalk11.bold("\nStarting ingest of copied sources...\n"));
22638
22732
  const manifest = new SourceManifestManager(marvinDir);
22639
22733
  manifest.scan();
22640
- const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) => path17.basename(i.targetPath));
22734
+ const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) => path18.basename(i.targetPath));
22641
22735
  for (const fileName of copiedFileNames) {
22642
22736
  try {
22643
22737
  await ingestFile({
@@ -23040,7 +23134,8 @@ The contributor is identifying a project risk.
23040
23134
  - Create actions for risk mitigation tasks
23041
23135
  - Create decisions for risk response strategies
23042
23136
  - Create questions for risks needing further assessment
23043
- - Tag all related artifacts with "risk" for tracking`,
23137
+ - Tag all related artifacts with "risk" for tracking
23138
+ - When a risk is resolved, use the update tool to remove the "risk" tag and add "risk-mitigated" so it no longer inflates the GAR quality metric`,
23044
23139
  "blocker-report": `
23045
23140
  ### Type-Specific Guidance: Blocker Report
23046
23141
  The contributor is reporting a blocker.
@@ -23524,12 +23619,38 @@ async function webCommand(options) {
23524
23619
  await startWebServer({ port, open: options.open });
23525
23620
  }
23526
23621
 
23622
+ // src/cli/commands/generate.ts
23623
+ import * as fs18 from "fs";
23624
+ import * as path19 from "path";
23625
+ import chalk18 from "chalk";
23626
+ import { confirm as confirm2 } from "@inquirer/prompts";
23627
+ async function generateClaudeMdCommand(options) {
23628
+ const project = loadProject();
23629
+ const filePath = path19.join(project.marvinDir, "CLAUDE.md");
23630
+ if (fs18.existsSync(filePath) && !options.force) {
23631
+ const overwrite = await confirm2({
23632
+ message: ".marvin/CLAUDE.md already exists. Overwrite?",
23633
+ default: false
23634
+ });
23635
+ if (!overwrite) {
23636
+ console.log(chalk18.dim("Aborted."));
23637
+ return;
23638
+ }
23639
+ }
23640
+ fs18.writeFileSync(
23641
+ filePath,
23642
+ getDefaultClaudeMdContent(project.config.name),
23643
+ "utf-8"
23644
+ );
23645
+ console.log(chalk18.green("Created .marvin/CLAUDE.md"));
23646
+ }
23647
+
23527
23648
  // src/cli/program.ts
23528
23649
  function createProgram() {
23529
23650
  const program = new Command();
23530
23651
  program.name("marvin").description(
23531
23652
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
23532
- ).version("0.3.5");
23653
+ ).version("0.3.7");
23533
23654
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
23534
23655
  await initCommand();
23535
23656
  });
@@ -23615,6 +23736,10 @@ function createProgram() {
23615
23736
  program.command("web").description("Launch a local web dashboard for project data").option("-p, --port <port>", "Port to listen on (default: 3000)").option("--no-open", "Don't auto-open the browser").action(async (options) => {
23616
23737
  await webCommand(options);
23617
23738
  });
23739
+ const generateCmd = program.command("generate").description("Generate project files");
23740
+ generateCmd.command("claude-md").description("Generate .marvin/CLAUDE.md project instruction file").option("--force", "Overwrite existing file without prompting").action(async (options) => {
23741
+ await generateClaudeMdCommand(options);
23742
+ });
23618
23743
  return program;
23619
23744
  }
23620
23745
  export {