mrvn-cli 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +332 -207
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +59 -8
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +337 -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,8 +20886,14 @@ function collectTools(marvinDir) {
|
|
|
20857
20886
|
const allSkillIds = [...allSkills.keys()];
|
|
20858
20887
|
const codeSkillTools = getSkillTools(allSkillIds, allSkills, store);
|
|
20859
20888
|
const skillsWithActions = allSkillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
|
|
20860
|
-
const projectRoot =
|
|
20889
|
+
const projectRoot = path10.dirname(marvinDir);
|
|
20861
20890
|
const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
|
|
20891
|
+
const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
|
|
20892
|
+
const navGroups = buildNavGroups({
|
|
20893
|
+
pluginRegs: registrations,
|
|
20894
|
+
skillRegs: allSkillRegs,
|
|
20895
|
+
pluginName: plugin?.name
|
|
20896
|
+
});
|
|
20862
20897
|
return [
|
|
20863
20898
|
...createDecisionTools(store),
|
|
20864
20899
|
...createActionTools(store),
|
|
@@ -20866,6 +20901,7 @@ function collectTools(marvinDir) {
|
|
|
20866
20901
|
...createDocumentTools(store),
|
|
20867
20902
|
...manifest ? createSourceTools(manifest) : [],
|
|
20868
20903
|
...createSessionTools(sessionStore),
|
|
20904
|
+
...createWebTools(store, config2.name, navGroups),
|
|
20869
20905
|
...pluginTools,
|
|
20870
20906
|
...codeSkillTools,
|
|
20871
20907
|
...actionTools
|
|
@@ -20917,11 +20953,63 @@ async function startStdioServer(options) {
|
|
|
20917
20953
|
import { Command } from "commander";
|
|
20918
20954
|
|
|
20919
20955
|
// src/cli/commands/init.ts
|
|
20920
|
-
import * as
|
|
20921
|
-
import * as
|
|
20956
|
+
import * as fs11 from "fs";
|
|
20957
|
+
import * as path11 from "path";
|
|
20922
20958
|
import * as YAML6 from "yaml";
|
|
20923
20959
|
import chalk2 from "chalk";
|
|
20924
20960
|
import { input, confirm, select } from "@inquirer/prompts";
|
|
20961
|
+
|
|
20962
|
+
// src/templates/claude-md.ts
|
|
20963
|
+
function getDefaultClaudeMdContent(projectName) {
|
|
20964
|
+
return `# Marvin \u2014 Project Instructions for "${projectName}"
|
|
20965
|
+
|
|
20966
|
+
You are **Marvin**, an AI-powered product development assistant.
|
|
20967
|
+
You operate as one of three personas \u2014 stay in role and suggest switching when a question falls outside your scope.
|
|
20968
|
+
|
|
20969
|
+
## Personas
|
|
20970
|
+
|
|
20971
|
+
| Persona | Short | Focus |
|
|
20972
|
+
|---------|-------|-------|
|
|
20973
|
+
| Product Owner | po | Vision, backlog, requirements, features, acceptance criteria |
|
|
20974
|
+
| Delivery Manager | dm | Planning, risks, actions, timelines, sprints, status |
|
|
20975
|
+
| Tech Lead | tl | Architecture, trade-offs, technical decisions, code quality |
|
|
20976
|
+
|
|
20977
|
+
## Proactive Governance
|
|
20978
|
+
|
|
20979
|
+
When conversation implies a commitment, risk, or open question, **suggest creating the matching artifact**:
|
|
20980
|
+
- A decision was made \u2192 offer to create a **Decision (D-xxx)**
|
|
20981
|
+
- Someone committed to a task \u2192 offer an **Action (A-xxx)** with owner and due date
|
|
20982
|
+
- An unanswered question surfaced \u2192 offer a **Question (Q-xxx)**
|
|
20983
|
+
- A new capability is discussed \u2192 offer a **Feature (F-xxx)**
|
|
20984
|
+
- Implementation scope is agreed \u2192 offer an **Epic (E-xxx)** linked to a feature
|
|
20985
|
+
- Work is being time-boxed \u2192 offer a **Sprint (SP-xxx)**
|
|
20986
|
+
|
|
20987
|
+
## Insights
|
|
20988
|
+
|
|
20989
|
+
Proactively flag:
|
|
20990
|
+
- Overdue actions or unresolved questions
|
|
20991
|
+
- Decisions without rationale or linked features
|
|
20992
|
+
- Features without linked epics
|
|
20993
|
+
- Risks mentioned but not tracked
|
|
20994
|
+
- When a risk is resolved \u2192 remove the "risk" tag and add "risk-mitigated"
|
|
20995
|
+
|
|
20996
|
+
## Tool Usage
|
|
20997
|
+
|
|
20998
|
+
- **Search before creating** \u2014 avoid duplicate artifacts
|
|
20999
|
+
- **Reference IDs** (e.g. D-001, A-003) when discussing existing items
|
|
21000
|
+
- **Link artifacts** \u2014 epics to features, actions to decisions, etc.
|
|
21001
|
+
- Use \`search_documents\` to find related context before answering
|
|
21002
|
+
|
|
21003
|
+
## Communication Style
|
|
21004
|
+
|
|
21005
|
+
- Be concise and structured
|
|
21006
|
+
- State assumptions explicitly
|
|
21007
|
+
- Use bullet points and tables where they aid clarity
|
|
21008
|
+
- When uncertain, ask a clarifying question rather than guessing
|
|
21009
|
+
`;
|
|
21010
|
+
}
|
|
21011
|
+
|
|
21012
|
+
// src/cli/commands/init.ts
|
|
20925
21013
|
async function initCommand() {
|
|
20926
21014
|
const cwd = process.cwd();
|
|
20927
21015
|
if (isMarvinProject(cwd)) {
|
|
@@ -20932,7 +21020,7 @@ async function initCommand() {
|
|
|
20932
21020
|
}
|
|
20933
21021
|
const projectName = await input({
|
|
20934
21022
|
message: "Project name:",
|
|
20935
|
-
default:
|
|
21023
|
+
default: path11.basename(cwd)
|
|
20936
21024
|
});
|
|
20937
21025
|
const methodology = await select({
|
|
20938
21026
|
message: "Methodology:",
|
|
@@ -20944,21 +21032,21 @@ async function initCommand() {
|
|
|
20944
21032
|
});
|
|
20945
21033
|
const plugin = resolvePlugin(methodology);
|
|
20946
21034
|
const registrations = plugin?.documentTypeRegistrations ?? [];
|
|
20947
|
-
const marvinDir =
|
|
21035
|
+
const marvinDir = path11.join(cwd, ".marvin");
|
|
20948
21036
|
const dirs = [
|
|
20949
21037
|
marvinDir,
|
|
20950
|
-
|
|
20951
|
-
|
|
20952
|
-
|
|
20953
|
-
|
|
20954
|
-
|
|
20955
|
-
|
|
21038
|
+
path11.join(marvinDir, "templates"),
|
|
21039
|
+
path11.join(marvinDir, "docs", "decisions"),
|
|
21040
|
+
path11.join(marvinDir, "docs", "actions"),
|
|
21041
|
+
path11.join(marvinDir, "docs", "questions"),
|
|
21042
|
+
path11.join(marvinDir, "sources"),
|
|
21043
|
+
path11.join(marvinDir, "skills")
|
|
20956
21044
|
];
|
|
20957
21045
|
for (const reg of registrations) {
|
|
20958
|
-
dirs.push(
|
|
21046
|
+
dirs.push(path11.join(marvinDir, "docs", reg.dirName));
|
|
20959
21047
|
}
|
|
20960
21048
|
for (const dir of dirs) {
|
|
20961
|
-
|
|
21049
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
20962
21050
|
}
|
|
20963
21051
|
const config2 = {
|
|
20964
21052
|
name: projectName,
|
|
@@ -20972,16 +21060,22 @@ async function initCommand() {
|
|
|
20972
21060
|
if (methodology === "sap-aem") {
|
|
20973
21061
|
config2.aem = { currentPhase: "assess-use-case" };
|
|
20974
21062
|
}
|
|
20975
|
-
|
|
20976
|
-
|
|
21063
|
+
fs11.writeFileSync(
|
|
21064
|
+
path11.join(marvinDir, "config.yaml"),
|
|
20977
21065
|
YAML6.stringify(config2),
|
|
20978
21066
|
"utf-8"
|
|
20979
21067
|
);
|
|
21068
|
+
fs11.writeFileSync(
|
|
21069
|
+
path11.join(marvinDir, "CLAUDE.md"),
|
|
21070
|
+
getDefaultClaudeMdContent(projectName),
|
|
21071
|
+
"utf-8"
|
|
21072
|
+
);
|
|
20980
21073
|
console.log(chalk2.green(`
|
|
20981
21074
|
Initialized Marvin project "${projectName}" in ${cwd}`));
|
|
20982
21075
|
console.log(chalk2.dim(`Methodology: ${plugin?.name ?? methodology}`));
|
|
20983
21076
|
console.log(chalk2.dim("\nCreated:"));
|
|
20984
21077
|
console.log(chalk2.dim(" .marvin/config.yaml"));
|
|
21078
|
+
console.log(chalk2.dim(" .marvin/CLAUDE.md"));
|
|
20985
21079
|
console.log(chalk2.dim(" .marvin/docs/decisions/"));
|
|
20986
21080
|
console.log(chalk2.dim(" .marvin/docs/actions/"));
|
|
20987
21081
|
console.log(chalk2.dim(" .marvin/docs/questions/"));
|
|
@@ -20999,18 +21093,18 @@ Initialized Marvin project "${projectName}" in ${cwd}`));
|
|
|
20999
21093
|
const sourceDir = await input({
|
|
21000
21094
|
message: "Path to directory containing source documents:"
|
|
21001
21095
|
});
|
|
21002
|
-
const resolvedDir =
|
|
21003
|
-
if (
|
|
21096
|
+
const resolvedDir = path11.resolve(sourceDir);
|
|
21097
|
+
if (fs11.existsSync(resolvedDir) && fs11.statSync(resolvedDir).isDirectory()) {
|
|
21004
21098
|
const sourceExts = [".pdf", ".md", ".txt"];
|
|
21005
|
-
const files =
|
|
21006
|
-
const ext =
|
|
21099
|
+
const files = fs11.readdirSync(resolvedDir).filter((f) => {
|
|
21100
|
+
const ext = path11.extname(f).toLowerCase();
|
|
21007
21101
|
return sourceExts.includes(ext);
|
|
21008
21102
|
});
|
|
21009
21103
|
let copied = 0;
|
|
21010
21104
|
for (const file2 of files) {
|
|
21011
|
-
const src =
|
|
21012
|
-
const dest =
|
|
21013
|
-
|
|
21105
|
+
const src = path11.join(resolvedDir, file2);
|
|
21106
|
+
const dest = path11.join(marvinDir, "sources", file2);
|
|
21107
|
+
fs11.copyFileSync(src, dest);
|
|
21014
21108
|
copied++;
|
|
21015
21109
|
}
|
|
21016
21110
|
if (copied > 0) {
|
|
@@ -21300,13 +21394,13 @@ async function setApiKey() {
|
|
|
21300
21394
|
}
|
|
21301
21395
|
|
|
21302
21396
|
// src/cli/commands/ingest.ts
|
|
21303
|
-
import * as
|
|
21304
|
-
import * as
|
|
21397
|
+
import * as fs13 from "fs";
|
|
21398
|
+
import * as path13 from "path";
|
|
21305
21399
|
import chalk8 from "chalk";
|
|
21306
21400
|
|
|
21307
21401
|
// src/sources/ingest.ts
|
|
21308
|
-
import * as
|
|
21309
|
-
import * as
|
|
21402
|
+
import * as fs12 from "fs";
|
|
21403
|
+
import * as path12 from "path";
|
|
21310
21404
|
import chalk7 from "chalk";
|
|
21311
21405
|
import ora2 from "ora";
|
|
21312
21406
|
import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -21409,15 +21503,15 @@ async function ingestFile(options) {
|
|
|
21409
21503
|
const persona = getPersona(personaId);
|
|
21410
21504
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21411
21505
|
const sourcesDir = manifest.sourcesDir;
|
|
21412
|
-
const filePath =
|
|
21413
|
-
if (!
|
|
21506
|
+
const filePath = path12.join(sourcesDir, fileName);
|
|
21507
|
+
if (!fs12.existsSync(filePath)) {
|
|
21414
21508
|
throw new Error(`Source file not found: ${filePath}`);
|
|
21415
21509
|
}
|
|
21416
|
-
const ext =
|
|
21510
|
+
const ext = path12.extname(fileName).toLowerCase();
|
|
21417
21511
|
const isPdf = ext === ".pdf";
|
|
21418
21512
|
let fileContent = null;
|
|
21419
21513
|
if (!isPdf) {
|
|
21420
|
-
fileContent =
|
|
21514
|
+
fileContent = fs12.readFileSync(filePath, "utf-8");
|
|
21421
21515
|
}
|
|
21422
21516
|
const store = new DocumentStore(marvinDir);
|
|
21423
21517
|
const createdArtifacts = [];
|
|
@@ -21520,9 +21614,9 @@ Ingest ended with error: ${message.subtype}`)
|
|
|
21520
21614
|
async function ingestCommand(file2, options) {
|
|
21521
21615
|
const project = loadProject();
|
|
21522
21616
|
const marvinDir = project.marvinDir;
|
|
21523
|
-
const sourcesDir =
|
|
21524
|
-
if (!
|
|
21525
|
-
|
|
21617
|
+
const sourcesDir = path13.join(marvinDir, "sources");
|
|
21618
|
+
if (!fs13.existsSync(sourcesDir)) {
|
|
21619
|
+
fs13.mkdirSync(sourcesDir, { recursive: true });
|
|
21526
21620
|
}
|
|
21527
21621
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21528
21622
|
manifest.scan();
|
|
@@ -21533,8 +21627,8 @@ async function ingestCommand(file2, options) {
|
|
|
21533
21627
|
return;
|
|
21534
21628
|
}
|
|
21535
21629
|
if (file2) {
|
|
21536
|
-
const filePath =
|
|
21537
|
-
if (!
|
|
21630
|
+
const filePath = path13.join(sourcesDir, file2);
|
|
21631
|
+
if (!fs13.existsSync(filePath)) {
|
|
21538
21632
|
console.log(chalk8.red(`Source file not found: ${file2}`));
|
|
21539
21633
|
console.log(chalk8.dim(`Expected at: ${filePath}`));
|
|
21540
21634
|
console.log(chalk8.dim(`Drop files into .marvin/sources/ and try again.`));
|
|
@@ -21601,7 +21695,7 @@ import ora3 from "ora";
|
|
|
21601
21695
|
import { input as input3 } from "@inquirer/prompts";
|
|
21602
21696
|
|
|
21603
21697
|
// src/git/repository.ts
|
|
21604
|
-
import * as
|
|
21698
|
+
import * as path14 from "path";
|
|
21605
21699
|
import simpleGit from "simple-git";
|
|
21606
21700
|
var MARVIN_GITIGNORE = `node_modules/
|
|
21607
21701
|
.DS_Store
|
|
@@ -21621,7 +21715,7 @@ var DIR_TYPE_LABELS = {
|
|
|
21621
21715
|
function buildCommitMessage(files) {
|
|
21622
21716
|
const counts = /* @__PURE__ */ new Map();
|
|
21623
21717
|
for (const f of files) {
|
|
21624
|
-
const parts2 = f.split(
|
|
21718
|
+
const parts2 = f.split(path14.sep).join("/").split("/");
|
|
21625
21719
|
const docsIdx = parts2.indexOf("docs");
|
|
21626
21720
|
if (docsIdx !== -1 && docsIdx + 1 < parts2.length) {
|
|
21627
21721
|
const dirName = parts2[docsIdx + 1];
|
|
@@ -21661,9 +21755,9 @@ var MarvinGit = class {
|
|
|
21661
21755
|
);
|
|
21662
21756
|
}
|
|
21663
21757
|
await this.git.init();
|
|
21664
|
-
const { writeFileSync:
|
|
21665
|
-
|
|
21666
|
-
|
|
21758
|
+
const { writeFileSync: writeFileSync10 } = await import("fs");
|
|
21759
|
+
writeFileSync10(
|
|
21760
|
+
path14.join(this.marvinDir, ".gitignore"),
|
|
21667
21761
|
MARVIN_GITIGNORE,
|
|
21668
21762
|
"utf-8"
|
|
21669
21763
|
);
|
|
@@ -21783,9 +21877,9 @@ var MarvinGit = class {
|
|
|
21783
21877
|
}
|
|
21784
21878
|
}
|
|
21785
21879
|
static async clone(url2, targetDir) {
|
|
21786
|
-
const marvinDir =
|
|
21787
|
-
const { existsSync:
|
|
21788
|
-
if (
|
|
21880
|
+
const marvinDir = path14.join(targetDir, ".marvin");
|
|
21881
|
+
const { existsSync: existsSync17 } = await import("fs");
|
|
21882
|
+
if (existsSync17(marvinDir)) {
|
|
21789
21883
|
throw new GitSyncError(
|
|
21790
21884
|
`.marvin/ already exists at ${targetDir}. Remove it first or choose a different directory.`
|
|
21791
21885
|
);
|
|
@@ -21969,8 +22063,8 @@ async function serveCommand() {
|
|
|
21969
22063
|
}
|
|
21970
22064
|
|
|
21971
22065
|
// src/cli/commands/skills.ts
|
|
21972
|
-
import * as
|
|
21973
|
-
import * as
|
|
22066
|
+
import * as fs14 from "fs";
|
|
22067
|
+
import * as path15 from "path";
|
|
21974
22068
|
import * as YAML7 from "yaml";
|
|
21975
22069
|
import matter3 from "gray-matter";
|
|
21976
22070
|
import chalk10 from "chalk";
|
|
@@ -22076,14 +22170,14 @@ async function skillsRemoveCommand(skillId, options) {
|
|
|
22076
22170
|
}
|
|
22077
22171
|
async function skillsCreateCommand(name) {
|
|
22078
22172
|
const project = loadProject();
|
|
22079
|
-
const skillsDir =
|
|
22080
|
-
|
|
22081
|
-
const skillDir =
|
|
22082
|
-
if (
|
|
22173
|
+
const skillsDir = path15.join(project.marvinDir, "skills");
|
|
22174
|
+
fs14.mkdirSync(skillsDir, { recursive: true });
|
|
22175
|
+
const skillDir = path15.join(skillsDir, name);
|
|
22176
|
+
if (fs14.existsSync(skillDir)) {
|
|
22083
22177
|
console.log(chalk10.yellow(`Skill directory already exists: ${skillDir}`));
|
|
22084
22178
|
return;
|
|
22085
22179
|
}
|
|
22086
|
-
|
|
22180
|
+
fs14.mkdirSync(skillDir, { recursive: true });
|
|
22087
22181
|
const displayName = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
22088
22182
|
const frontmatter = {
|
|
22089
22183
|
name,
|
|
@@ -22097,7 +22191,7 @@ async function skillsCreateCommand(name) {
|
|
|
22097
22191
|
You have the **${displayName}** skill.
|
|
22098
22192
|
`;
|
|
22099
22193
|
const skillMd = matter3.stringify(body, frontmatter);
|
|
22100
|
-
|
|
22194
|
+
fs14.writeFileSync(path15.join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
22101
22195
|
const actions = [
|
|
22102
22196
|
{
|
|
22103
22197
|
id: "run",
|
|
@@ -22107,7 +22201,7 @@ You have the **${displayName}** skill.
|
|
|
22107
22201
|
maxTurns: 5
|
|
22108
22202
|
}
|
|
22109
22203
|
];
|
|
22110
|
-
|
|
22204
|
+
fs14.writeFileSync(path15.join(skillDir, "actions.yaml"), YAML7.stringify(actions), "utf-8");
|
|
22111
22205
|
console.log(chalk10.green(`Created skill: ${skillDir}/`));
|
|
22112
22206
|
console.log(chalk10.dim(" SKILL.md \u2014 skill definition and prompt"));
|
|
22113
22207
|
console.log(chalk10.dim(" actions.yaml \u2014 action definitions"));
|
|
@@ -22115,14 +22209,14 @@ You have the **${displayName}** skill.
|
|
|
22115
22209
|
}
|
|
22116
22210
|
async function skillsMigrateCommand() {
|
|
22117
22211
|
const project = loadProject();
|
|
22118
|
-
const skillsDir =
|
|
22119
|
-
if (!
|
|
22212
|
+
const skillsDir = path15.join(project.marvinDir, "skills");
|
|
22213
|
+
if (!fs14.existsSync(skillsDir)) {
|
|
22120
22214
|
console.log(chalk10.dim("No skills directory found."));
|
|
22121
22215
|
return;
|
|
22122
22216
|
}
|
|
22123
22217
|
let entries;
|
|
22124
22218
|
try {
|
|
22125
|
-
entries =
|
|
22219
|
+
entries = fs14.readdirSync(skillsDir);
|
|
22126
22220
|
} catch {
|
|
22127
22221
|
console.log(chalk10.red("Could not read skills directory."));
|
|
22128
22222
|
return;
|
|
@@ -22134,16 +22228,16 @@ async function skillsMigrateCommand() {
|
|
|
22134
22228
|
}
|
|
22135
22229
|
let migrated = 0;
|
|
22136
22230
|
for (const file2 of yamlFiles) {
|
|
22137
|
-
const yamlPath =
|
|
22231
|
+
const yamlPath = path15.join(skillsDir, file2);
|
|
22138
22232
|
const baseName = file2.replace(/\.(yaml|yml)$/, "");
|
|
22139
|
-
const outputDir =
|
|
22140
|
-
if (
|
|
22233
|
+
const outputDir = path15.join(skillsDir, baseName);
|
|
22234
|
+
if (fs14.existsSync(outputDir)) {
|
|
22141
22235
|
console.log(chalk10.yellow(`Skipping "${file2}" \u2014 directory "${baseName}/" already exists.`));
|
|
22142
22236
|
continue;
|
|
22143
22237
|
}
|
|
22144
22238
|
try {
|
|
22145
22239
|
migrateYamlToSkillMd(yamlPath, outputDir);
|
|
22146
|
-
|
|
22240
|
+
fs14.renameSync(yamlPath, `${yamlPath}.bak`);
|
|
22147
22241
|
console.log(chalk10.green(`Migrated "${file2}" \u2192 "${baseName}/"`));
|
|
22148
22242
|
migrated++;
|
|
22149
22243
|
} catch (err) {
|
|
@@ -22157,35 +22251,35 @@ ${migrated} skill(s) migrated. Original files renamed to *.bak`));
|
|
|
22157
22251
|
}
|
|
22158
22252
|
|
|
22159
22253
|
// src/cli/commands/import.ts
|
|
22160
|
-
import * as
|
|
22161
|
-
import * as
|
|
22254
|
+
import * as fs17 from "fs";
|
|
22255
|
+
import * as path18 from "path";
|
|
22162
22256
|
import chalk11 from "chalk";
|
|
22163
22257
|
|
|
22164
22258
|
// src/import/engine.ts
|
|
22165
|
-
import * as
|
|
22166
|
-
import * as
|
|
22259
|
+
import * as fs16 from "fs";
|
|
22260
|
+
import * as path17 from "path";
|
|
22167
22261
|
import matter5 from "gray-matter";
|
|
22168
22262
|
|
|
22169
22263
|
// src/import/classifier.ts
|
|
22170
|
-
import * as
|
|
22171
|
-
import * as
|
|
22264
|
+
import * as fs15 from "fs";
|
|
22265
|
+
import * as path16 from "path";
|
|
22172
22266
|
import matter4 from "gray-matter";
|
|
22173
22267
|
var RAW_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".txt"]);
|
|
22174
22268
|
var CORE_DIR_NAMES = /* @__PURE__ */ new Set(["decisions", "actions", "questions"]);
|
|
22175
22269
|
var ID_PATTERN = /^[A-Z]+-\d{3,}$/;
|
|
22176
22270
|
function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
22177
|
-
const resolved =
|
|
22178
|
-
const stat =
|
|
22271
|
+
const resolved = path16.resolve(inputPath);
|
|
22272
|
+
const stat = fs15.statSync(resolved);
|
|
22179
22273
|
if (!stat.isDirectory()) {
|
|
22180
22274
|
return classifyFile(resolved, knownTypes);
|
|
22181
22275
|
}
|
|
22182
|
-
if (
|
|
22276
|
+
if (path16.basename(resolved) === ".marvin" || fs15.existsSync(path16.join(resolved, "config.yaml"))) {
|
|
22183
22277
|
return { type: "marvin-project", inputPath: resolved };
|
|
22184
22278
|
}
|
|
22185
22279
|
const allDirNames = /* @__PURE__ */ new Set([...CORE_DIR_NAMES, ...knownDirNames]);
|
|
22186
|
-
const entries =
|
|
22280
|
+
const entries = fs15.readdirSync(resolved);
|
|
22187
22281
|
const hasDocSubdirs = entries.some(
|
|
22188
|
-
(e) => allDirNames.has(e) &&
|
|
22282
|
+
(e) => allDirNames.has(e) && fs15.statSync(path16.join(resolved, e)).isDirectory()
|
|
22189
22283
|
);
|
|
22190
22284
|
if (hasDocSubdirs) {
|
|
22191
22285
|
return { type: "docs-directory", inputPath: resolved };
|
|
@@ -22194,7 +22288,7 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22194
22288
|
if (mdFiles.length > 0) {
|
|
22195
22289
|
const hasMarvinDocs = mdFiles.some((f) => {
|
|
22196
22290
|
try {
|
|
22197
|
-
const raw =
|
|
22291
|
+
const raw = fs15.readFileSync(path16.join(resolved, f), "utf-8");
|
|
22198
22292
|
const { data } = matter4(raw);
|
|
22199
22293
|
return isValidMarvinDocument(data, knownTypes);
|
|
22200
22294
|
} catch {
|
|
@@ -22208,14 +22302,14 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22208
22302
|
return { type: "raw-source-dir", inputPath: resolved };
|
|
22209
22303
|
}
|
|
22210
22304
|
function classifyFile(filePath, knownTypes) {
|
|
22211
|
-
const resolved =
|
|
22212
|
-
const ext =
|
|
22305
|
+
const resolved = path16.resolve(filePath);
|
|
22306
|
+
const ext = path16.extname(resolved).toLowerCase();
|
|
22213
22307
|
if (RAW_SOURCE_EXTENSIONS.has(ext)) {
|
|
22214
22308
|
return { type: "raw-source-file", inputPath: resolved };
|
|
22215
22309
|
}
|
|
22216
22310
|
if (ext === ".md") {
|
|
22217
22311
|
try {
|
|
22218
|
-
const raw =
|
|
22312
|
+
const raw = fs15.readFileSync(resolved, "utf-8");
|
|
22219
22313
|
const { data } = matter4(raw);
|
|
22220
22314
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22221
22315
|
return { type: "marvin-document", inputPath: resolved };
|
|
@@ -22340,9 +22434,9 @@ function executeImportPlan(plan, store, marvinDir, options) {
|
|
|
22340
22434
|
continue;
|
|
22341
22435
|
}
|
|
22342
22436
|
if (item.action === "copy") {
|
|
22343
|
-
const targetDir =
|
|
22344
|
-
|
|
22345
|
-
|
|
22437
|
+
const targetDir = path17.dirname(item.targetPath);
|
|
22438
|
+
fs16.mkdirSync(targetDir, { recursive: true });
|
|
22439
|
+
fs16.copyFileSync(item.sourcePath, item.targetPath);
|
|
22346
22440
|
copied++;
|
|
22347
22441
|
continue;
|
|
22348
22442
|
}
|
|
@@ -22378,19 +22472,19 @@ function formatPlanSummary(plan) {
|
|
|
22378
22472
|
lines.push(`Documents to import: ${imports.length}`);
|
|
22379
22473
|
for (const item of imports) {
|
|
22380
22474
|
const idInfo = item.originalId !== item.newId ? `${item.originalId} \u2192 ${item.newId}` : item.newId ?? item.originalId ?? "";
|
|
22381
|
-
lines.push(` ${idInfo} ${
|
|
22475
|
+
lines.push(` ${idInfo} ${path17.basename(item.sourcePath)}`);
|
|
22382
22476
|
}
|
|
22383
22477
|
}
|
|
22384
22478
|
if (copies.length > 0) {
|
|
22385
22479
|
lines.push(`Files to copy to sources/: ${copies.length}`);
|
|
22386
22480
|
for (const item of copies) {
|
|
22387
|
-
lines.push(` ${
|
|
22481
|
+
lines.push(` ${path17.basename(item.sourcePath)} \u2192 ${path17.basename(item.targetPath)}`);
|
|
22388
22482
|
}
|
|
22389
22483
|
}
|
|
22390
22484
|
if (skips.length > 0) {
|
|
22391
22485
|
lines.push(`Skipped (conflict): ${skips.length}`);
|
|
22392
22486
|
for (const item of skips) {
|
|
22393
|
-
lines.push(` ${item.originalId ??
|
|
22487
|
+
lines.push(` ${item.originalId ?? path17.basename(item.sourcePath)} ${item.reason ?? ""}`);
|
|
22394
22488
|
}
|
|
22395
22489
|
}
|
|
22396
22490
|
if (plan.items.length === 0) {
|
|
@@ -22423,11 +22517,11 @@ function getDirNameForType(store, type) {
|
|
|
22423
22517
|
}
|
|
22424
22518
|
function collectMarvinDocs(dir, knownTypes) {
|
|
22425
22519
|
const docs = [];
|
|
22426
|
-
const files =
|
|
22520
|
+
const files = fs16.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
22427
22521
|
for (const file2 of files) {
|
|
22428
|
-
const filePath =
|
|
22522
|
+
const filePath = path17.join(dir, file2);
|
|
22429
22523
|
try {
|
|
22430
|
-
const raw =
|
|
22524
|
+
const raw = fs16.readFileSync(filePath, "utf-8");
|
|
22431
22525
|
const { data, content } = matter5(raw);
|
|
22432
22526
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22433
22527
|
docs.push({
|
|
@@ -22483,23 +22577,23 @@ function planDocImports(docs, store, options) {
|
|
|
22483
22577
|
}
|
|
22484
22578
|
function planFromMarvinProject(classification, store, _marvinDir, options) {
|
|
22485
22579
|
let projectDir = classification.inputPath;
|
|
22486
|
-
if (
|
|
22487
|
-
const inner =
|
|
22488
|
-
if (
|
|
22580
|
+
if (path17.basename(projectDir) !== ".marvin") {
|
|
22581
|
+
const inner = path17.join(projectDir, ".marvin");
|
|
22582
|
+
if (fs16.existsSync(inner)) {
|
|
22489
22583
|
projectDir = inner;
|
|
22490
22584
|
}
|
|
22491
22585
|
}
|
|
22492
|
-
const docsDir =
|
|
22493
|
-
if (!
|
|
22586
|
+
const docsDir = path17.join(projectDir, "docs");
|
|
22587
|
+
if (!fs16.existsSync(docsDir)) {
|
|
22494
22588
|
return [];
|
|
22495
22589
|
}
|
|
22496
22590
|
const knownTypes = store.registeredTypes;
|
|
22497
22591
|
const allDocs = [];
|
|
22498
|
-
const subdirs =
|
|
22499
|
-
(d) =>
|
|
22592
|
+
const subdirs = fs16.readdirSync(docsDir).filter(
|
|
22593
|
+
(d) => fs16.statSync(path17.join(docsDir, d)).isDirectory()
|
|
22500
22594
|
);
|
|
22501
22595
|
for (const subdir of subdirs) {
|
|
22502
|
-
const docs = collectMarvinDocs(
|
|
22596
|
+
const docs = collectMarvinDocs(path17.join(docsDir, subdir), knownTypes);
|
|
22503
22597
|
allDocs.push(...docs);
|
|
22504
22598
|
}
|
|
22505
22599
|
return planDocImports(allDocs, store, options);
|
|
@@ -22509,10 +22603,10 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22509
22603
|
const knownTypes = store.registeredTypes;
|
|
22510
22604
|
const allDocs = [];
|
|
22511
22605
|
allDocs.push(...collectMarvinDocs(dir, knownTypes));
|
|
22512
|
-
const entries =
|
|
22606
|
+
const entries = fs16.readdirSync(dir);
|
|
22513
22607
|
for (const entry of entries) {
|
|
22514
|
-
const entryPath =
|
|
22515
|
-
if (
|
|
22608
|
+
const entryPath = path17.join(dir, entry);
|
|
22609
|
+
if (fs16.statSync(entryPath).isDirectory()) {
|
|
22516
22610
|
allDocs.push(...collectMarvinDocs(entryPath, knownTypes));
|
|
22517
22611
|
}
|
|
22518
22612
|
}
|
|
@@ -22521,7 +22615,7 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22521
22615
|
function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
22522
22616
|
const filePath = classification.inputPath;
|
|
22523
22617
|
const knownTypes = store.registeredTypes;
|
|
22524
|
-
const raw =
|
|
22618
|
+
const raw = fs16.readFileSync(filePath, "utf-8");
|
|
22525
22619
|
const { data, content } = matter5(raw);
|
|
22526
22620
|
if (!isValidMarvinDocument(data, knownTypes)) {
|
|
22527
22621
|
return [];
|
|
@@ -22537,14 +22631,14 @@ function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
|
22537
22631
|
}
|
|
22538
22632
|
function planFromRawSourceDir(classification, marvinDir) {
|
|
22539
22633
|
const dir = classification.inputPath;
|
|
22540
|
-
const sourcesDir =
|
|
22634
|
+
const sourcesDir = path17.join(marvinDir, "sources");
|
|
22541
22635
|
const items = [];
|
|
22542
|
-
const files =
|
|
22543
|
-
const stat =
|
|
22636
|
+
const files = fs16.readdirSync(dir).filter((f) => {
|
|
22637
|
+
const stat = fs16.statSync(path17.join(dir, f));
|
|
22544
22638
|
return stat.isFile();
|
|
22545
22639
|
});
|
|
22546
22640
|
for (const file2 of files) {
|
|
22547
|
-
const sourcePath =
|
|
22641
|
+
const sourcePath = path17.join(dir, file2);
|
|
22548
22642
|
const targetPath = resolveSourceFileName(sourcesDir, file2);
|
|
22549
22643
|
items.push({
|
|
22550
22644
|
action: "copy",
|
|
@@ -22555,8 +22649,8 @@ function planFromRawSourceDir(classification, marvinDir) {
|
|
|
22555
22649
|
return items;
|
|
22556
22650
|
}
|
|
22557
22651
|
function planFromRawSourceFile(classification, marvinDir) {
|
|
22558
|
-
const sourcesDir =
|
|
22559
|
-
const fileName =
|
|
22652
|
+
const sourcesDir = path17.join(marvinDir, "sources");
|
|
22653
|
+
const fileName = path17.basename(classification.inputPath);
|
|
22560
22654
|
const targetPath = resolveSourceFileName(sourcesDir, fileName);
|
|
22561
22655
|
return [
|
|
22562
22656
|
{
|
|
@@ -22567,25 +22661,25 @@ function planFromRawSourceFile(classification, marvinDir) {
|
|
|
22567
22661
|
];
|
|
22568
22662
|
}
|
|
22569
22663
|
function resolveSourceFileName(sourcesDir, fileName) {
|
|
22570
|
-
const targetPath =
|
|
22571
|
-
if (!
|
|
22664
|
+
const targetPath = path17.join(sourcesDir, fileName);
|
|
22665
|
+
if (!fs16.existsSync(targetPath)) {
|
|
22572
22666
|
return targetPath;
|
|
22573
22667
|
}
|
|
22574
|
-
const ext =
|
|
22575
|
-
const base =
|
|
22668
|
+
const ext = path17.extname(fileName);
|
|
22669
|
+
const base = path17.basename(fileName, ext);
|
|
22576
22670
|
let counter = 1;
|
|
22577
22671
|
let candidate;
|
|
22578
22672
|
do {
|
|
22579
|
-
candidate =
|
|
22673
|
+
candidate = path17.join(sourcesDir, `${base}-${counter}${ext}`);
|
|
22580
22674
|
counter++;
|
|
22581
|
-
} while (
|
|
22675
|
+
} while (fs16.existsSync(candidate));
|
|
22582
22676
|
return candidate;
|
|
22583
22677
|
}
|
|
22584
22678
|
|
|
22585
22679
|
// src/cli/commands/import.ts
|
|
22586
22680
|
async function importCommand(inputPath, options) {
|
|
22587
|
-
const resolved =
|
|
22588
|
-
if (!
|
|
22681
|
+
const resolved = path18.resolve(inputPath);
|
|
22682
|
+
if (!fs17.existsSync(resolved)) {
|
|
22589
22683
|
throw new ImportError(`Path not found: ${resolved}`);
|
|
22590
22684
|
}
|
|
22591
22685
|
const project = loadProject();
|
|
@@ -22637,7 +22731,7 @@ async function importCommand(inputPath, options) {
|
|
|
22637
22731
|
console.log(chalk11.bold("\nStarting ingest of copied sources...\n"));
|
|
22638
22732
|
const manifest = new SourceManifestManager(marvinDir);
|
|
22639
22733
|
manifest.scan();
|
|
22640
|
-
const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) =>
|
|
22734
|
+
const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) => path18.basename(i.targetPath));
|
|
22641
22735
|
for (const fileName of copiedFileNames) {
|
|
22642
22736
|
try {
|
|
22643
22737
|
await ingestFile({
|
|
@@ -23040,7 +23134,8 @@ The contributor is identifying a project risk.
|
|
|
23040
23134
|
- Create actions for risk mitigation tasks
|
|
23041
23135
|
- Create decisions for risk response strategies
|
|
23042
23136
|
- Create questions for risks needing further assessment
|
|
23043
|
-
- Tag all related artifacts with "risk" for tracking
|
|
23137
|
+
- Tag all related artifacts with "risk" for tracking
|
|
23138
|
+
- When a risk is resolved, use the update tool to remove the "risk" tag and add "risk-mitigated" so it no longer inflates the GAR quality metric`,
|
|
23044
23139
|
"blocker-report": `
|
|
23045
23140
|
### Type-Specific Guidance: Blocker Report
|
|
23046
23141
|
The contributor is reporting a blocker.
|
|
@@ -23524,12 +23619,38 @@ async function webCommand(options) {
|
|
|
23524
23619
|
await startWebServer({ port, open: options.open });
|
|
23525
23620
|
}
|
|
23526
23621
|
|
|
23622
|
+
// src/cli/commands/generate.ts
|
|
23623
|
+
import * as fs18 from "fs";
|
|
23624
|
+
import * as path19 from "path";
|
|
23625
|
+
import chalk18 from "chalk";
|
|
23626
|
+
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
23627
|
+
async function generateClaudeMdCommand(options) {
|
|
23628
|
+
const project = loadProject();
|
|
23629
|
+
const filePath = path19.join(project.marvinDir, "CLAUDE.md");
|
|
23630
|
+
if (fs18.existsSync(filePath) && !options.force) {
|
|
23631
|
+
const overwrite = await confirm2({
|
|
23632
|
+
message: ".marvin/CLAUDE.md already exists. Overwrite?",
|
|
23633
|
+
default: false
|
|
23634
|
+
});
|
|
23635
|
+
if (!overwrite) {
|
|
23636
|
+
console.log(chalk18.dim("Aborted."));
|
|
23637
|
+
return;
|
|
23638
|
+
}
|
|
23639
|
+
}
|
|
23640
|
+
fs18.writeFileSync(
|
|
23641
|
+
filePath,
|
|
23642
|
+
getDefaultClaudeMdContent(project.config.name),
|
|
23643
|
+
"utf-8"
|
|
23644
|
+
);
|
|
23645
|
+
console.log(chalk18.green("Created .marvin/CLAUDE.md"));
|
|
23646
|
+
}
|
|
23647
|
+
|
|
23527
23648
|
// src/cli/program.ts
|
|
23528
23649
|
function createProgram() {
|
|
23529
23650
|
const program = new Command();
|
|
23530
23651
|
program.name("marvin").description(
|
|
23531
23652
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
23532
|
-
).version("0.3.
|
|
23653
|
+
).version("0.3.7");
|
|
23533
23654
|
program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
23534
23655
|
await initCommand();
|
|
23535
23656
|
});
|
|
@@ -23615,6 +23736,10 @@ function createProgram() {
|
|
|
23615
23736
|
program.command("web").description("Launch a local web dashboard for project data").option("-p, --port <port>", "Port to listen on (default: 3000)").option("--no-open", "Don't auto-open the browser").action(async (options) => {
|
|
23616
23737
|
await webCommand(options);
|
|
23617
23738
|
});
|
|
23739
|
+
const generateCmd = program.command("generate").description("Generate project files");
|
|
23740
|
+
generateCmd.command("claude-md").description("Generate .marvin/CLAUDE.md project instruction file").option("--force", "Overwrite existing file without prompting").action(async (options) => {
|
|
23741
|
+
await generateClaudeMdCommand(options);
|
|
23742
|
+
});
|
|
23618
23743
|
return program;
|
|
23619
23744
|
}
|
|
23620
23745
|
export {
|