mrvn-cli 0.3.5 → 0.3.6

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,7 +20886,7 @@ 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 });
20862
20891
  return [
20863
20892
  ...createDecisionTools(store),
@@ -20917,11 +20946,63 @@ async function startStdioServer(options) {
20917
20946
  import { Command } from "commander";
20918
20947
 
20919
20948
  // src/cli/commands/init.ts
20920
- import * as fs10 from "fs";
20921
- import * as path10 from "path";
20949
+ import * as fs11 from "fs";
20950
+ import * as path11 from "path";
20922
20951
  import * as YAML6 from "yaml";
20923
20952
  import chalk2 from "chalk";
20924
20953
  import { input, confirm, select } from "@inquirer/prompts";
20954
+
20955
+ // src/templates/claude-md.ts
20956
+ function getDefaultClaudeMdContent(projectName) {
20957
+ return `# Marvin \u2014 Project Instructions for "${projectName}"
20958
+
20959
+ You are **Marvin**, an AI-powered product development assistant.
20960
+ You operate as one of three personas \u2014 stay in role and suggest switching when a question falls outside your scope.
20961
+
20962
+ ## Personas
20963
+
20964
+ | Persona | Short | Focus |
20965
+ |---------|-------|-------|
20966
+ | Product Owner | po | Vision, backlog, requirements, features, acceptance criteria |
20967
+ | Delivery Manager | dm | Planning, risks, actions, timelines, sprints, status |
20968
+ | Tech Lead | tl | Architecture, trade-offs, technical decisions, code quality |
20969
+
20970
+ ## Proactive Governance
20971
+
20972
+ When conversation implies a commitment, risk, or open question, **suggest creating the matching artifact**:
20973
+ - A decision was made \u2192 offer to create a **Decision (D-xxx)**
20974
+ - Someone committed to a task \u2192 offer an **Action (A-xxx)** with owner and due date
20975
+ - An unanswered question surfaced \u2192 offer a **Question (Q-xxx)**
20976
+ - A new capability is discussed \u2192 offer a **Feature (F-xxx)**
20977
+ - Implementation scope is agreed \u2192 offer an **Epic (E-xxx)** linked to a feature
20978
+ - Work is being time-boxed \u2192 offer a **Sprint (SP-xxx)**
20979
+
20980
+ ## Insights
20981
+
20982
+ Proactively flag:
20983
+ - Overdue actions or unresolved questions
20984
+ - Decisions without rationale or linked features
20985
+ - Features without linked epics
20986
+ - Risks mentioned but not tracked
20987
+ - When a risk is resolved \u2192 remove the "risk" tag and add "risk-mitigated"
20988
+
20989
+ ## Tool Usage
20990
+
20991
+ - **Search before creating** \u2014 avoid duplicate artifacts
20992
+ - **Reference IDs** (e.g. D-001, A-003) when discussing existing items
20993
+ - **Link artifacts** \u2014 epics to features, actions to decisions, etc.
20994
+ - Use \`search_documents\` to find related context before answering
20995
+
20996
+ ## Communication Style
20997
+
20998
+ - Be concise and structured
20999
+ - State assumptions explicitly
21000
+ - Use bullet points and tables where they aid clarity
21001
+ - When uncertain, ask a clarifying question rather than guessing
21002
+ `;
21003
+ }
21004
+
21005
+ // src/cli/commands/init.ts
20925
21006
  async function initCommand() {
20926
21007
  const cwd = process.cwd();
20927
21008
  if (isMarvinProject(cwd)) {
@@ -20932,7 +21013,7 @@ async function initCommand() {
20932
21013
  }
20933
21014
  const projectName = await input({
20934
21015
  message: "Project name:",
20935
- default: path10.basename(cwd)
21016
+ default: path11.basename(cwd)
20936
21017
  });
20937
21018
  const methodology = await select({
20938
21019
  message: "Methodology:",
@@ -20944,21 +21025,21 @@ async function initCommand() {
20944
21025
  });
20945
21026
  const plugin = resolvePlugin(methodology);
20946
21027
  const registrations = plugin?.documentTypeRegistrations ?? [];
20947
- const marvinDir = path10.join(cwd, ".marvin");
21028
+ const marvinDir = path11.join(cwd, ".marvin");
20948
21029
  const dirs = [
20949
21030
  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")
21031
+ path11.join(marvinDir, "templates"),
21032
+ path11.join(marvinDir, "docs", "decisions"),
21033
+ path11.join(marvinDir, "docs", "actions"),
21034
+ path11.join(marvinDir, "docs", "questions"),
21035
+ path11.join(marvinDir, "sources"),
21036
+ path11.join(marvinDir, "skills")
20956
21037
  ];
20957
21038
  for (const reg of registrations) {
20958
- dirs.push(path10.join(marvinDir, "docs", reg.dirName));
21039
+ dirs.push(path11.join(marvinDir, "docs", reg.dirName));
20959
21040
  }
20960
21041
  for (const dir of dirs) {
20961
- fs10.mkdirSync(dir, { recursive: true });
21042
+ fs11.mkdirSync(dir, { recursive: true });
20962
21043
  }
20963
21044
  const config2 = {
20964
21045
  name: projectName,
@@ -20972,16 +21053,22 @@ async function initCommand() {
20972
21053
  if (methodology === "sap-aem") {
20973
21054
  config2.aem = { currentPhase: "assess-use-case" };
20974
21055
  }
20975
- fs10.writeFileSync(
20976
- path10.join(marvinDir, "config.yaml"),
21056
+ fs11.writeFileSync(
21057
+ path11.join(marvinDir, "config.yaml"),
20977
21058
  YAML6.stringify(config2),
20978
21059
  "utf-8"
20979
21060
  );
21061
+ fs11.writeFileSync(
21062
+ path11.join(marvinDir, "CLAUDE.md"),
21063
+ getDefaultClaudeMdContent(projectName),
21064
+ "utf-8"
21065
+ );
20980
21066
  console.log(chalk2.green(`
20981
21067
  Initialized Marvin project "${projectName}" in ${cwd}`));
20982
21068
  console.log(chalk2.dim(`Methodology: ${plugin?.name ?? methodology}`));
20983
21069
  console.log(chalk2.dim("\nCreated:"));
20984
21070
  console.log(chalk2.dim(" .marvin/config.yaml"));
21071
+ console.log(chalk2.dim(" .marvin/CLAUDE.md"));
20985
21072
  console.log(chalk2.dim(" .marvin/docs/decisions/"));
20986
21073
  console.log(chalk2.dim(" .marvin/docs/actions/"));
20987
21074
  console.log(chalk2.dim(" .marvin/docs/questions/"));
@@ -20999,18 +21086,18 @@ Initialized Marvin project "${projectName}" in ${cwd}`));
20999
21086
  const sourceDir = await input({
21000
21087
  message: "Path to directory containing source documents:"
21001
21088
  });
21002
- const resolvedDir = path10.resolve(sourceDir);
21003
- if (fs10.existsSync(resolvedDir) && fs10.statSync(resolvedDir).isDirectory()) {
21089
+ const resolvedDir = path11.resolve(sourceDir);
21090
+ if (fs11.existsSync(resolvedDir) && fs11.statSync(resolvedDir).isDirectory()) {
21004
21091
  const sourceExts = [".pdf", ".md", ".txt"];
21005
- const files = fs10.readdirSync(resolvedDir).filter((f) => {
21006
- const ext = path10.extname(f).toLowerCase();
21092
+ const files = fs11.readdirSync(resolvedDir).filter((f) => {
21093
+ const ext = path11.extname(f).toLowerCase();
21007
21094
  return sourceExts.includes(ext);
21008
21095
  });
21009
21096
  let copied = 0;
21010
21097
  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);
21098
+ const src = path11.join(resolvedDir, file2);
21099
+ const dest = path11.join(marvinDir, "sources", file2);
21100
+ fs11.copyFileSync(src, dest);
21014
21101
  copied++;
21015
21102
  }
21016
21103
  if (copied > 0) {
@@ -21300,13 +21387,13 @@ async function setApiKey() {
21300
21387
  }
21301
21388
 
21302
21389
  // src/cli/commands/ingest.ts
21303
- import * as fs12 from "fs";
21304
- import * as path12 from "path";
21390
+ import * as fs13 from "fs";
21391
+ import * as path13 from "path";
21305
21392
  import chalk8 from "chalk";
21306
21393
 
21307
21394
  // src/sources/ingest.ts
21308
- import * as fs11 from "fs";
21309
- import * as path11 from "path";
21395
+ import * as fs12 from "fs";
21396
+ import * as path12 from "path";
21310
21397
  import chalk7 from "chalk";
21311
21398
  import ora2 from "ora";
21312
21399
  import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
@@ -21409,15 +21496,15 @@ async function ingestFile(options) {
21409
21496
  const persona = getPersona(personaId);
21410
21497
  const manifest = new SourceManifestManager(marvinDir);
21411
21498
  const sourcesDir = manifest.sourcesDir;
21412
- const filePath = path11.join(sourcesDir, fileName);
21413
- if (!fs11.existsSync(filePath)) {
21499
+ const filePath = path12.join(sourcesDir, fileName);
21500
+ if (!fs12.existsSync(filePath)) {
21414
21501
  throw new Error(`Source file not found: ${filePath}`);
21415
21502
  }
21416
- const ext = path11.extname(fileName).toLowerCase();
21503
+ const ext = path12.extname(fileName).toLowerCase();
21417
21504
  const isPdf = ext === ".pdf";
21418
21505
  let fileContent = null;
21419
21506
  if (!isPdf) {
21420
- fileContent = fs11.readFileSync(filePath, "utf-8");
21507
+ fileContent = fs12.readFileSync(filePath, "utf-8");
21421
21508
  }
21422
21509
  const store = new DocumentStore(marvinDir);
21423
21510
  const createdArtifacts = [];
@@ -21520,9 +21607,9 @@ Ingest ended with error: ${message.subtype}`)
21520
21607
  async function ingestCommand(file2, options) {
21521
21608
  const project = loadProject();
21522
21609
  const marvinDir = project.marvinDir;
21523
- const sourcesDir = path12.join(marvinDir, "sources");
21524
- if (!fs12.existsSync(sourcesDir)) {
21525
- fs12.mkdirSync(sourcesDir, { recursive: true });
21610
+ const sourcesDir = path13.join(marvinDir, "sources");
21611
+ if (!fs13.existsSync(sourcesDir)) {
21612
+ fs13.mkdirSync(sourcesDir, { recursive: true });
21526
21613
  }
21527
21614
  const manifest = new SourceManifestManager(marvinDir);
21528
21615
  manifest.scan();
@@ -21533,8 +21620,8 @@ async function ingestCommand(file2, options) {
21533
21620
  return;
21534
21621
  }
21535
21622
  if (file2) {
21536
- const filePath = path12.join(sourcesDir, file2);
21537
- if (!fs12.existsSync(filePath)) {
21623
+ const filePath = path13.join(sourcesDir, file2);
21624
+ if (!fs13.existsSync(filePath)) {
21538
21625
  console.log(chalk8.red(`Source file not found: ${file2}`));
21539
21626
  console.log(chalk8.dim(`Expected at: ${filePath}`));
21540
21627
  console.log(chalk8.dim(`Drop files into .marvin/sources/ and try again.`));
@@ -21601,7 +21688,7 @@ import ora3 from "ora";
21601
21688
  import { input as input3 } from "@inquirer/prompts";
21602
21689
 
21603
21690
  // src/git/repository.ts
21604
- import * as path13 from "path";
21691
+ import * as path14 from "path";
21605
21692
  import simpleGit from "simple-git";
21606
21693
  var MARVIN_GITIGNORE = `node_modules/
21607
21694
  .DS_Store
@@ -21621,7 +21708,7 @@ var DIR_TYPE_LABELS = {
21621
21708
  function buildCommitMessage(files) {
21622
21709
  const counts = /* @__PURE__ */ new Map();
21623
21710
  for (const f of files) {
21624
- const parts2 = f.split(path13.sep).join("/").split("/");
21711
+ const parts2 = f.split(path14.sep).join("/").split("/");
21625
21712
  const docsIdx = parts2.indexOf("docs");
21626
21713
  if (docsIdx !== -1 && docsIdx + 1 < parts2.length) {
21627
21714
  const dirName = parts2[docsIdx + 1];
@@ -21661,9 +21748,9 @@ var MarvinGit = class {
21661
21748
  );
21662
21749
  }
21663
21750
  await this.git.init();
21664
- const { writeFileSync: writeFileSync9 } = await import("fs");
21665
- writeFileSync9(
21666
- path13.join(this.marvinDir, ".gitignore"),
21751
+ const { writeFileSync: writeFileSync10 } = await import("fs");
21752
+ writeFileSync10(
21753
+ path14.join(this.marvinDir, ".gitignore"),
21667
21754
  MARVIN_GITIGNORE,
21668
21755
  "utf-8"
21669
21756
  );
@@ -21783,9 +21870,9 @@ var MarvinGit = class {
21783
21870
  }
21784
21871
  }
21785
21872
  static async clone(url2, targetDir) {
21786
- const marvinDir = path13.join(targetDir, ".marvin");
21787
- const { existsSync: existsSync16 } = await import("fs");
21788
- if (existsSync16(marvinDir)) {
21873
+ const marvinDir = path14.join(targetDir, ".marvin");
21874
+ const { existsSync: existsSync17 } = await import("fs");
21875
+ if (existsSync17(marvinDir)) {
21789
21876
  throw new GitSyncError(
21790
21877
  `.marvin/ already exists at ${targetDir}. Remove it first or choose a different directory.`
21791
21878
  );
@@ -21969,8 +22056,8 @@ async function serveCommand() {
21969
22056
  }
21970
22057
 
21971
22058
  // src/cli/commands/skills.ts
21972
- import * as fs13 from "fs";
21973
- import * as path14 from "path";
22059
+ import * as fs14 from "fs";
22060
+ import * as path15 from "path";
21974
22061
  import * as YAML7 from "yaml";
21975
22062
  import matter3 from "gray-matter";
21976
22063
  import chalk10 from "chalk";
@@ -22076,14 +22163,14 @@ async function skillsRemoveCommand(skillId, options) {
22076
22163
  }
22077
22164
  async function skillsCreateCommand(name) {
22078
22165
  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)) {
22166
+ const skillsDir = path15.join(project.marvinDir, "skills");
22167
+ fs14.mkdirSync(skillsDir, { recursive: true });
22168
+ const skillDir = path15.join(skillsDir, name);
22169
+ if (fs14.existsSync(skillDir)) {
22083
22170
  console.log(chalk10.yellow(`Skill directory already exists: ${skillDir}`));
22084
22171
  return;
22085
22172
  }
22086
- fs13.mkdirSync(skillDir, { recursive: true });
22173
+ fs14.mkdirSync(skillDir, { recursive: true });
22087
22174
  const displayName = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
22088
22175
  const frontmatter = {
22089
22176
  name,
@@ -22097,7 +22184,7 @@ async function skillsCreateCommand(name) {
22097
22184
  You have the **${displayName}** skill.
22098
22185
  `;
22099
22186
  const skillMd = matter3.stringify(body, frontmatter);
22100
- fs13.writeFileSync(path14.join(skillDir, "SKILL.md"), skillMd, "utf-8");
22187
+ fs14.writeFileSync(path15.join(skillDir, "SKILL.md"), skillMd, "utf-8");
22101
22188
  const actions = [
22102
22189
  {
22103
22190
  id: "run",
@@ -22107,7 +22194,7 @@ You have the **${displayName}** skill.
22107
22194
  maxTurns: 5
22108
22195
  }
22109
22196
  ];
22110
- fs13.writeFileSync(path14.join(skillDir, "actions.yaml"), YAML7.stringify(actions), "utf-8");
22197
+ fs14.writeFileSync(path15.join(skillDir, "actions.yaml"), YAML7.stringify(actions), "utf-8");
22111
22198
  console.log(chalk10.green(`Created skill: ${skillDir}/`));
22112
22199
  console.log(chalk10.dim(" SKILL.md \u2014 skill definition and prompt"));
22113
22200
  console.log(chalk10.dim(" actions.yaml \u2014 action definitions"));
@@ -22115,14 +22202,14 @@ You have the **${displayName}** skill.
22115
22202
  }
22116
22203
  async function skillsMigrateCommand() {
22117
22204
  const project = loadProject();
22118
- const skillsDir = path14.join(project.marvinDir, "skills");
22119
- if (!fs13.existsSync(skillsDir)) {
22205
+ const skillsDir = path15.join(project.marvinDir, "skills");
22206
+ if (!fs14.existsSync(skillsDir)) {
22120
22207
  console.log(chalk10.dim("No skills directory found."));
22121
22208
  return;
22122
22209
  }
22123
22210
  let entries;
22124
22211
  try {
22125
- entries = fs13.readdirSync(skillsDir);
22212
+ entries = fs14.readdirSync(skillsDir);
22126
22213
  } catch {
22127
22214
  console.log(chalk10.red("Could not read skills directory."));
22128
22215
  return;
@@ -22134,16 +22221,16 @@ async function skillsMigrateCommand() {
22134
22221
  }
22135
22222
  let migrated = 0;
22136
22223
  for (const file2 of yamlFiles) {
22137
- const yamlPath = path14.join(skillsDir, file2);
22224
+ const yamlPath = path15.join(skillsDir, file2);
22138
22225
  const baseName = file2.replace(/\.(yaml|yml)$/, "");
22139
- const outputDir = path14.join(skillsDir, baseName);
22140
- if (fs13.existsSync(outputDir)) {
22226
+ const outputDir = path15.join(skillsDir, baseName);
22227
+ if (fs14.existsSync(outputDir)) {
22141
22228
  console.log(chalk10.yellow(`Skipping "${file2}" \u2014 directory "${baseName}/" already exists.`));
22142
22229
  continue;
22143
22230
  }
22144
22231
  try {
22145
22232
  migrateYamlToSkillMd(yamlPath, outputDir);
22146
- fs13.renameSync(yamlPath, `${yamlPath}.bak`);
22233
+ fs14.renameSync(yamlPath, `${yamlPath}.bak`);
22147
22234
  console.log(chalk10.green(`Migrated "${file2}" \u2192 "${baseName}/"`));
22148
22235
  migrated++;
22149
22236
  } catch (err) {
@@ -22157,35 +22244,35 @@ ${migrated} skill(s) migrated. Original files renamed to *.bak`));
22157
22244
  }
22158
22245
 
22159
22246
  // src/cli/commands/import.ts
22160
- import * as fs16 from "fs";
22161
- import * as path17 from "path";
22247
+ import * as fs17 from "fs";
22248
+ import * as path18 from "path";
22162
22249
  import chalk11 from "chalk";
22163
22250
 
22164
22251
  // src/import/engine.ts
22165
- import * as fs15 from "fs";
22166
- import * as path16 from "path";
22252
+ import * as fs16 from "fs";
22253
+ import * as path17 from "path";
22167
22254
  import matter5 from "gray-matter";
22168
22255
 
22169
22256
  // src/import/classifier.ts
22170
- import * as fs14 from "fs";
22171
- import * as path15 from "path";
22257
+ import * as fs15 from "fs";
22258
+ import * as path16 from "path";
22172
22259
  import matter4 from "gray-matter";
22173
22260
  var RAW_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".txt"]);
22174
22261
  var CORE_DIR_NAMES = /* @__PURE__ */ new Set(["decisions", "actions", "questions"]);
22175
22262
  var ID_PATTERN = /^[A-Z]+-\d{3,}$/;
22176
22263
  function classifyPath(inputPath, knownTypes, knownDirNames) {
22177
- const resolved = path15.resolve(inputPath);
22178
- const stat = fs14.statSync(resolved);
22264
+ const resolved = path16.resolve(inputPath);
22265
+ const stat = fs15.statSync(resolved);
22179
22266
  if (!stat.isDirectory()) {
22180
22267
  return classifyFile(resolved, knownTypes);
22181
22268
  }
22182
- if (path15.basename(resolved) === ".marvin" || fs14.existsSync(path15.join(resolved, "config.yaml"))) {
22269
+ if (path16.basename(resolved) === ".marvin" || fs15.existsSync(path16.join(resolved, "config.yaml"))) {
22183
22270
  return { type: "marvin-project", inputPath: resolved };
22184
22271
  }
22185
22272
  const allDirNames = /* @__PURE__ */ new Set([...CORE_DIR_NAMES, ...knownDirNames]);
22186
- const entries = fs14.readdirSync(resolved);
22273
+ const entries = fs15.readdirSync(resolved);
22187
22274
  const hasDocSubdirs = entries.some(
22188
- (e) => allDirNames.has(e) && fs14.statSync(path15.join(resolved, e)).isDirectory()
22275
+ (e) => allDirNames.has(e) && fs15.statSync(path16.join(resolved, e)).isDirectory()
22189
22276
  );
22190
22277
  if (hasDocSubdirs) {
22191
22278
  return { type: "docs-directory", inputPath: resolved };
@@ -22194,7 +22281,7 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
22194
22281
  if (mdFiles.length > 0) {
22195
22282
  const hasMarvinDocs = mdFiles.some((f) => {
22196
22283
  try {
22197
- const raw = fs14.readFileSync(path15.join(resolved, f), "utf-8");
22284
+ const raw = fs15.readFileSync(path16.join(resolved, f), "utf-8");
22198
22285
  const { data } = matter4(raw);
22199
22286
  return isValidMarvinDocument(data, knownTypes);
22200
22287
  } catch {
@@ -22208,14 +22295,14 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
22208
22295
  return { type: "raw-source-dir", inputPath: resolved };
22209
22296
  }
22210
22297
  function classifyFile(filePath, knownTypes) {
22211
- const resolved = path15.resolve(filePath);
22212
- const ext = path15.extname(resolved).toLowerCase();
22298
+ const resolved = path16.resolve(filePath);
22299
+ const ext = path16.extname(resolved).toLowerCase();
22213
22300
  if (RAW_SOURCE_EXTENSIONS.has(ext)) {
22214
22301
  return { type: "raw-source-file", inputPath: resolved };
22215
22302
  }
22216
22303
  if (ext === ".md") {
22217
22304
  try {
22218
- const raw = fs14.readFileSync(resolved, "utf-8");
22305
+ const raw = fs15.readFileSync(resolved, "utf-8");
22219
22306
  const { data } = matter4(raw);
22220
22307
  if (isValidMarvinDocument(data, knownTypes)) {
22221
22308
  return { type: "marvin-document", inputPath: resolved };
@@ -22340,9 +22427,9 @@ function executeImportPlan(plan, store, marvinDir, options) {
22340
22427
  continue;
22341
22428
  }
22342
22429
  if (item.action === "copy") {
22343
- const targetDir = path16.dirname(item.targetPath);
22344
- fs15.mkdirSync(targetDir, { recursive: true });
22345
- fs15.copyFileSync(item.sourcePath, item.targetPath);
22430
+ const targetDir = path17.dirname(item.targetPath);
22431
+ fs16.mkdirSync(targetDir, { recursive: true });
22432
+ fs16.copyFileSync(item.sourcePath, item.targetPath);
22346
22433
  copied++;
22347
22434
  continue;
22348
22435
  }
@@ -22378,19 +22465,19 @@ function formatPlanSummary(plan) {
22378
22465
  lines.push(`Documents to import: ${imports.length}`);
22379
22466
  for (const item of imports) {
22380
22467
  const idInfo = item.originalId !== item.newId ? `${item.originalId} \u2192 ${item.newId}` : item.newId ?? item.originalId ?? "";
22381
- lines.push(` ${idInfo} ${path16.basename(item.sourcePath)}`);
22468
+ lines.push(` ${idInfo} ${path17.basename(item.sourcePath)}`);
22382
22469
  }
22383
22470
  }
22384
22471
  if (copies.length > 0) {
22385
22472
  lines.push(`Files to copy to sources/: ${copies.length}`);
22386
22473
  for (const item of copies) {
22387
- lines.push(` ${path16.basename(item.sourcePath)} \u2192 ${path16.basename(item.targetPath)}`);
22474
+ lines.push(` ${path17.basename(item.sourcePath)} \u2192 ${path17.basename(item.targetPath)}`);
22388
22475
  }
22389
22476
  }
22390
22477
  if (skips.length > 0) {
22391
22478
  lines.push(`Skipped (conflict): ${skips.length}`);
22392
22479
  for (const item of skips) {
22393
- lines.push(` ${item.originalId ?? path16.basename(item.sourcePath)} ${item.reason ?? ""}`);
22480
+ lines.push(` ${item.originalId ?? path17.basename(item.sourcePath)} ${item.reason ?? ""}`);
22394
22481
  }
22395
22482
  }
22396
22483
  if (plan.items.length === 0) {
@@ -22423,11 +22510,11 @@ function getDirNameForType(store, type) {
22423
22510
  }
22424
22511
  function collectMarvinDocs(dir, knownTypes) {
22425
22512
  const docs = [];
22426
- const files = fs15.readdirSync(dir).filter((f) => f.endsWith(".md"));
22513
+ const files = fs16.readdirSync(dir).filter((f) => f.endsWith(".md"));
22427
22514
  for (const file2 of files) {
22428
- const filePath = path16.join(dir, file2);
22515
+ const filePath = path17.join(dir, file2);
22429
22516
  try {
22430
- const raw = fs15.readFileSync(filePath, "utf-8");
22517
+ const raw = fs16.readFileSync(filePath, "utf-8");
22431
22518
  const { data, content } = matter5(raw);
22432
22519
  if (isValidMarvinDocument(data, knownTypes)) {
22433
22520
  docs.push({
@@ -22483,23 +22570,23 @@ function planDocImports(docs, store, options) {
22483
22570
  }
22484
22571
  function planFromMarvinProject(classification, store, _marvinDir, options) {
22485
22572
  let projectDir = classification.inputPath;
22486
- if (path16.basename(projectDir) !== ".marvin") {
22487
- const inner = path16.join(projectDir, ".marvin");
22488
- if (fs15.existsSync(inner)) {
22573
+ if (path17.basename(projectDir) !== ".marvin") {
22574
+ const inner = path17.join(projectDir, ".marvin");
22575
+ if (fs16.existsSync(inner)) {
22489
22576
  projectDir = inner;
22490
22577
  }
22491
22578
  }
22492
- const docsDir = path16.join(projectDir, "docs");
22493
- if (!fs15.existsSync(docsDir)) {
22579
+ const docsDir = path17.join(projectDir, "docs");
22580
+ if (!fs16.existsSync(docsDir)) {
22494
22581
  return [];
22495
22582
  }
22496
22583
  const knownTypes = store.registeredTypes;
22497
22584
  const allDocs = [];
22498
- const subdirs = fs15.readdirSync(docsDir).filter(
22499
- (d) => fs15.statSync(path16.join(docsDir, d)).isDirectory()
22585
+ const subdirs = fs16.readdirSync(docsDir).filter(
22586
+ (d) => fs16.statSync(path17.join(docsDir, d)).isDirectory()
22500
22587
  );
22501
22588
  for (const subdir of subdirs) {
22502
- const docs = collectMarvinDocs(path16.join(docsDir, subdir), knownTypes);
22589
+ const docs = collectMarvinDocs(path17.join(docsDir, subdir), knownTypes);
22503
22590
  allDocs.push(...docs);
22504
22591
  }
22505
22592
  return planDocImports(allDocs, store, options);
@@ -22509,10 +22596,10 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
22509
22596
  const knownTypes = store.registeredTypes;
22510
22597
  const allDocs = [];
22511
22598
  allDocs.push(...collectMarvinDocs(dir, knownTypes));
22512
- const entries = fs15.readdirSync(dir);
22599
+ const entries = fs16.readdirSync(dir);
22513
22600
  for (const entry of entries) {
22514
- const entryPath = path16.join(dir, entry);
22515
- if (fs15.statSync(entryPath).isDirectory()) {
22601
+ const entryPath = path17.join(dir, entry);
22602
+ if (fs16.statSync(entryPath).isDirectory()) {
22516
22603
  allDocs.push(...collectMarvinDocs(entryPath, knownTypes));
22517
22604
  }
22518
22605
  }
@@ -22521,7 +22608,7 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
22521
22608
  function planFromSingleDocument(classification, store, _marvinDir, options) {
22522
22609
  const filePath = classification.inputPath;
22523
22610
  const knownTypes = store.registeredTypes;
22524
- const raw = fs15.readFileSync(filePath, "utf-8");
22611
+ const raw = fs16.readFileSync(filePath, "utf-8");
22525
22612
  const { data, content } = matter5(raw);
22526
22613
  if (!isValidMarvinDocument(data, knownTypes)) {
22527
22614
  return [];
@@ -22537,14 +22624,14 @@ function planFromSingleDocument(classification, store, _marvinDir, options) {
22537
22624
  }
22538
22625
  function planFromRawSourceDir(classification, marvinDir) {
22539
22626
  const dir = classification.inputPath;
22540
- const sourcesDir = path16.join(marvinDir, "sources");
22627
+ const sourcesDir = path17.join(marvinDir, "sources");
22541
22628
  const items = [];
22542
- const files = fs15.readdirSync(dir).filter((f) => {
22543
- const stat = fs15.statSync(path16.join(dir, f));
22629
+ const files = fs16.readdirSync(dir).filter((f) => {
22630
+ const stat = fs16.statSync(path17.join(dir, f));
22544
22631
  return stat.isFile();
22545
22632
  });
22546
22633
  for (const file2 of files) {
22547
- const sourcePath = path16.join(dir, file2);
22634
+ const sourcePath = path17.join(dir, file2);
22548
22635
  const targetPath = resolveSourceFileName(sourcesDir, file2);
22549
22636
  items.push({
22550
22637
  action: "copy",
@@ -22555,8 +22642,8 @@ function planFromRawSourceDir(classification, marvinDir) {
22555
22642
  return items;
22556
22643
  }
22557
22644
  function planFromRawSourceFile(classification, marvinDir) {
22558
- const sourcesDir = path16.join(marvinDir, "sources");
22559
- const fileName = path16.basename(classification.inputPath);
22645
+ const sourcesDir = path17.join(marvinDir, "sources");
22646
+ const fileName = path17.basename(classification.inputPath);
22560
22647
  const targetPath = resolveSourceFileName(sourcesDir, fileName);
22561
22648
  return [
22562
22649
  {
@@ -22567,25 +22654,25 @@ function planFromRawSourceFile(classification, marvinDir) {
22567
22654
  ];
22568
22655
  }
22569
22656
  function resolveSourceFileName(sourcesDir, fileName) {
22570
- const targetPath = path16.join(sourcesDir, fileName);
22571
- if (!fs15.existsSync(targetPath)) {
22657
+ const targetPath = path17.join(sourcesDir, fileName);
22658
+ if (!fs16.existsSync(targetPath)) {
22572
22659
  return targetPath;
22573
22660
  }
22574
- const ext = path16.extname(fileName);
22575
- const base = path16.basename(fileName, ext);
22661
+ const ext = path17.extname(fileName);
22662
+ const base = path17.basename(fileName, ext);
22576
22663
  let counter = 1;
22577
22664
  let candidate;
22578
22665
  do {
22579
- candidate = path16.join(sourcesDir, `${base}-${counter}${ext}`);
22666
+ candidate = path17.join(sourcesDir, `${base}-${counter}${ext}`);
22580
22667
  counter++;
22581
- } while (fs15.existsSync(candidate));
22668
+ } while (fs16.existsSync(candidate));
22582
22669
  return candidate;
22583
22670
  }
22584
22671
 
22585
22672
  // src/cli/commands/import.ts
22586
22673
  async function importCommand(inputPath, options) {
22587
- const resolved = path17.resolve(inputPath);
22588
- if (!fs16.existsSync(resolved)) {
22674
+ const resolved = path18.resolve(inputPath);
22675
+ if (!fs17.existsSync(resolved)) {
22589
22676
  throw new ImportError(`Path not found: ${resolved}`);
22590
22677
  }
22591
22678
  const project = loadProject();
@@ -22637,7 +22724,7 @@ async function importCommand(inputPath, options) {
22637
22724
  console.log(chalk11.bold("\nStarting ingest of copied sources...\n"));
22638
22725
  const manifest = new SourceManifestManager(marvinDir);
22639
22726
  manifest.scan();
22640
- const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) => path17.basename(i.targetPath));
22727
+ const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) => path18.basename(i.targetPath));
22641
22728
  for (const fileName of copiedFileNames) {
22642
22729
  try {
22643
22730
  await ingestFile({
@@ -23040,7 +23127,8 @@ The contributor is identifying a project risk.
23040
23127
  - Create actions for risk mitigation tasks
23041
23128
  - Create decisions for risk response strategies
23042
23129
  - Create questions for risks needing further assessment
23043
- - Tag all related artifacts with "risk" for tracking`,
23130
+ - Tag all related artifacts with "risk" for tracking
23131
+ - 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
23132
  "blocker-report": `
23045
23133
  ### Type-Specific Guidance: Blocker Report
23046
23134
  The contributor is reporting a blocker.
@@ -23524,12 +23612,38 @@ async function webCommand(options) {
23524
23612
  await startWebServer({ port, open: options.open });
23525
23613
  }
23526
23614
 
23615
+ // src/cli/commands/generate.ts
23616
+ import * as fs18 from "fs";
23617
+ import * as path19 from "path";
23618
+ import chalk18 from "chalk";
23619
+ import { confirm as confirm2 } from "@inquirer/prompts";
23620
+ async function generateClaudeMdCommand(options) {
23621
+ const project = loadProject();
23622
+ const filePath = path19.join(project.marvinDir, "CLAUDE.md");
23623
+ if (fs18.existsSync(filePath) && !options.force) {
23624
+ const overwrite = await confirm2({
23625
+ message: ".marvin/CLAUDE.md already exists. Overwrite?",
23626
+ default: false
23627
+ });
23628
+ if (!overwrite) {
23629
+ console.log(chalk18.dim("Aborted."));
23630
+ return;
23631
+ }
23632
+ }
23633
+ fs18.writeFileSync(
23634
+ filePath,
23635
+ getDefaultClaudeMdContent(project.config.name),
23636
+ "utf-8"
23637
+ );
23638
+ console.log(chalk18.green("Created .marvin/CLAUDE.md"));
23639
+ }
23640
+
23527
23641
  // src/cli/program.ts
23528
23642
  function createProgram() {
23529
23643
  const program = new Command();
23530
23644
  program.name("marvin").description(
23531
23645
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
23532
- ).version("0.3.5");
23646
+ ).version("0.3.6");
23533
23647
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
23534
23648
  await initCommand();
23535
23649
  });
@@ -23615,6 +23729,10 @@ function createProgram() {
23615
23729
  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
23730
  await webCommand(options);
23617
23731
  });
23732
+ const generateCmd = program.command("generate").description("Generate project files");
23733
+ generateCmd.command("claude-md").description("Generate .marvin/CLAUDE.md project instruction file").option("--force", "Overwrite existing file without prompting").action(async (options) => {
23734
+ await generateClaudeMdCommand(options);
23735
+ });
23618
23736
  return program;
23619
23737
  }
23620
23738
  export {