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.d.ts +1 -1
- package/dist/index.js +325 -207
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +22 -8
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +330 -214
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -526,9 +526,24 @@ function resolvePersonaId(input4) {
|
|
|
526
526
|
}
|
|
527
527
|
|
|
528
528
|
// src/personas/prompt-builder.ts
|
|
529
|
-
|
|
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,
|
|
1350
|
-
if (!
|
|
1364
|
+
function getElementAtPath(obj, path20) {
|
|
1365
|
+
if (!path20)
|
|
1351
1366
|
return obj;
|
|
1352
|
-
return
|
|
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(
|
|
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(
|
|
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,
|
|
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 = [...
|
|
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
|
|
1965
|
-
for (const seg of
|
|
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
|
|
13943
|
-
if (
|
|
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 (
|
|
13948
|
-
const key =
|
|
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 (
|
|
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
|
|
18791
|
-
import * as
|
|
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 =
|
|
18897
|
-
const raw =
|
|
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 =
|
|
18908
|
-
const raw =
|
|
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
|
-
|
|
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
|
|
19083
|
-
import * as
|
|
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(
|
|
19137
|
-
const url2 = `${this.baseUrl}${
|
|
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} ${
|
|
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
|
|
19660
|
+
return path6.join(path6.dirname(thisFile), "builtin");
|
|
19632
19661
|
}
|
|
19633
19662
|
function loadSkillFromDirectory(dirPath) {
|
|
19634
|
-
const skillMdPath =
|
|
19635
|
-
if (!
|
|
19663
|
+
const skillMdPath = path6.join(dirPath, "SKILL.md");
|
|
19664
|
+
if (!fs6.existsSync(skillMdPath)) return void 0;
|
|
19636
19665
|
try {
|
|
19637
|
-
const raw =
|
|
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 =
|
|
19649
|
-
if (
|
|
19677
|
+
const personasDir = path6.join(dirPath, "personas");
|
|
19678
|
+
if (fs6.existsSync(personasDir)) {
|
|
19650
19679
|
try {
|
|
19651
|
-
for (const file2 of
|
|
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 =
|
|
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 =
|
|
19664
|
-
if (
|
|
19692
|
+
const actionsPath = path6.join(dirPath, "actions.yaml");
|
|
19693
|
+
if (fs6.existsSync(actionsPath)) {
|
|
19665
19694
|
try {
|
|
19666
|
-
const actionsRaw =
|
|
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 (
|
|
19694
|
-
for (const entry of
|
|
19695
|
-
const entryPath =
|
|
19696
|
-
if (!
|
|
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 =
|
|
19706
|
-
if (
|
|
19734
|
+
const skillsDir = path6.join(marvinDir, "skills");
|
|
19735
|
+
if (fs6.existsSync(skillsDir)) {
|
|
19707
19736
|
let entries;
|
|
19708
19737
|
try {
|
|
19709
|
-
entries =
|
|
19738
|
+
entries = fs6.readdirSync(skillsDir);
|
|
19710
19739
|
} catch {
|
|
19711
19740
|
entries = [];
|
|
19712
19741
|
}
|
|
19713
19742
|
for (const entry of entries) {
|
|
19714
|
-
const entryPath =
|
|
19743
|
+
const entryPath = path6.join(skillsDir, entry);
|
|
19715
19744
|
try {
|
|
19716
|
-
if (
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
19855
|
-
|
|
19883
|
+
const personasDir = path6.join(outputDir, "personas");
|
|
19884
|
+
fs6.mkdirSync(personasDir, { recursive: true });
|
|
19856
19885
|
for (const personaId of personaKeys) {
|
|
19857
|
-
|
|
19858
|
-
|
|
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
|
-
|
|
19869
|
-
|
|
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
|
|
20113
|
-
import * as
|
|
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
|
|
20123
|
-
import * as
|
|
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 =
|
|
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 (!
|
|
20198
|
+
if (!fs7.existsSync(this.filePath)) return [];
|
|
20170
20199
|
try {
|
|
20171
|
-
const raw =
|
|
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 =
|
|
20181
|
-
if (!
|
|
20182
|
-
|
|
20209
|
+
const dir = path7.dirname(this.filePath);
|
|
20210
|
+
if (!fs7.existsSync(dir)) {
|
|
20211
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
20183
20212
|
}
|
|
20184
|
-
|
|
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
|
|
20221
|
-
import * as
|
|
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 =
|
|
20235
|
-
this.manifestPath =
|
|
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 (!
|
|
20268
|
+
if (!fs8.existsSync(this.manifestPath)) {
|
|
20240
20269
|
return emptyManifest();
|
|
20241
20270
|
}
|
|
20242
|
-
const raw =
|
|
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
|
-
|
|
20248
|
-
|
|
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 (!
|
|
20283
|
+
if (!fs8.existsSync(this.sourcesDir)) {
|
|
20255
20284
|
return { added, changed, removed };
|
|
20256
20285
|
}
|
|
20257
20286
|
const onDisk = new Set(
|
|
20258
|
-
|
|
20259
|
-
const ext =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
20343
|
-
const hasSourcesDir =
|
|
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
|
|
20553
|
-
import * as
|
|
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 =
|
|
20852
|
-
const hasSourcesDir =
|
|
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 =
|
|
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
|
|
20921
|
-
import * as
|
|
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:
|
|
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 =
|
|
21028
|
+
const marvinDir = path11.join(cwd, ".marvin");
|
|
20948
21029
|
const dirs = [
|
|
20949
21030
|
marvinDir,
|
|
20950
|
-
|
|
20951
|
-
|
|
20952
|
-
|
|
20953
|
-
|
|
20954
|
-
|
|
20955
|
-
|
|
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(
|
|
21039
|
+
dirs.push(path11.join(marvinDir, "docs", reg.dirName));
|
|
20959
21040
|
}
|
|
20960
21041
|
for (const dir of dirs) {
|
|
20961
|
-
|
|
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
|
-
|
|
20976
|
-
|
|
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 =
|
|
21003
|
-
if (
|
|
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 =
|
|
21006
|
-
const ext =
|
|
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 =
|
|
21012
|
-
const dest =
|
|
21013
|
-
|
|
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
|
|
21304
|
-
import * as
|
|
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
|
|
21309
|
-
import * as
|
|
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 =
|
|
21413
|
-
if (!
|
|
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 =
|
|
21503
|
+
const ext = path12.extname(fileName).toLowerCase();
|
|
21417
21504
|
const isPdf = ext === ".pdf";
|
|
21418
21505
|
let fileContent = null;
|
|
21419
21506
|
if (!isPdf) {
|
|
21420
|
-
fileContent =
|
|
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 =
|
|
21524
|
-
if (!
|
|
21525
|
-
|
|
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 =
|
|
21537
|
-
if (!
|
|
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
|
|
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(
|
|
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:
|
|
21665
|
-
|
|
21666
|
-
|
|
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 =
|
|
21787
|
-
const { existsSync:
|
|
21788
|
-
if (
|
|
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
|
|
21973
|
-
import * as
|
|
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 =
|
|
22080
|
-
|
|
22081
|
-
const skillDir =
|
|
22082
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
22119
|
-
if (!
|
|
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 =
|
|
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 =
|
|
22224
|
+
const yamlPath = path15.join(skillsDir, file2);
|
|
22138
22225
|
const baseName = file2.replace(/\.(yaml|yml)$/, "");
|
|
22139
|
-
const outputDir =
|
|
22140
|
-
if (
|
|
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
|
-
|
|
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
|
|
22161
|
-
import * as
|
|
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
|
|
22166
|
-
import * as
|
|
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
|
|
22171
|
-
import * as
|
|
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 =
|
|
22178
|
-
const stat =
|
|
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 (
|
|
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 =
|
|
22273
|
+
const entries = fs15.readdirSync(resolved);
|
|
22187
22274
|
const hasDocSubdirs = entries.some(
|
|
22188
|
-
(e) => allDirNames.has(e) &&
|
|
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 =
|
|
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 =
|
|
22212
|
-
const ext =
|
|
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 =
|
|
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 =
|
|
22344
|
-
|
|
22345
|
-
|
|
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} ${
|
|
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(` ${
|
|
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 ??
|
|
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 =
|
|
22513
|
+
const files = fs16.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
22427
22514
|
for (const file2 of files) {
|
|
22428
|
-
const filePath =
|
|
22515
|
+
const filePath = path17.join(dir, file2);
|
|
22429
22516
|
try {
|
|
22430
|
-
const raw =
|
|
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 (
|
|
22487
|
-
const inner =
|
|
22488
|
-
if (
|
|
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 =
|
|
22493
|
-
if (!
|
|
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 =
|
|
22499
|
-
(d) =>
|
|
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(
|
|
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 =
|
|
22599
|
+
const entries = fs16.readdirSync(dir);
|
|
22513
22600
|
for (const entry of entries) {
|
|
22514
|
-
const entryPath =
|
|
22515
|
-
if (
|
|
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 =
|
|
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 =
|
|
22627
|
+
const sourcesDir = path17.join(marvinDir, "sources");
|
|
22541
22628
|
const items = [];
|
|
22542
|
-
const files =
|
|
22543
|
-
const stat =
|
|
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 =
|
|
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 =
|
|
22559
|
-
const fileName =
|
|
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 =
|
|
22571
|
-
if (!
|
|
22657
|
+
const targetPath = path17.join(sourcesDir, fileName);
|
|
22658
|
+
if (!fs16.existsSync(targetPath)) {
|
|
22572
22659
|
return targetPath;
|
|
22573
22660
|
}
|
|
22574
|
-
const ext =
|
|
22575
|
-
const base =
|
|
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 =
|
|
22666
|
+
candidate = path17.join(sourcesDir, `${base}-${counter}${ext}`);
|
|
22580
22667
|
counter++;
|
|
22581
|
-
} while (
|
|
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 =
|
|
22588
|
-
if (!
|
|
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) =>
|
|
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.
|
|
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 {
|