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/marvin.js
CHANGED
|
@@ -920,10 +920,10 @@ function mergeDefs(...defs) {
|
|
|
920
920
|
function cloneDef(schema) {
|
|
921
921
|
return mergeDefs(schema._zod.def);
|
|
922
922
|
}
|
|
923
|
-
function getElementAtPath(obj,
|
|
924
|
-
if (!
|
|
923
|
+
function getElementAtPath(obj, path20) {
|
|
924
|
+
if (!path20)
|
|
925
925
|
return obj;
|
|
926
|
-
return
|
|
926
|
+
return path20.reduce((acc, key) => acc?.[key], obj);
|
|
927
927
|
}
|
|
928
928
|
function promiseAllObject(promisesObj) {
|
|
929
929
|
const keys = Object.keys(promisesObj);
|
|
@@ -1306,11 +1306,11 @@ function aborted(x, startIndex = 0) {
|
|
|
1306
1306
|
}
|
|
1307
1307
|
return false;
|
|
1308
1308
|
}
|
|
1309
|
-
function prefixIssues(
|
|
1309
|
+
function prefixIssues(path20, issues) {
|
|
1310
1310
|
return issues.map((iss) => {
|
|
1311
1311
|
var _a2;
|
|
1312
1312
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
1313
|
-
iss.path.unshift(
|
|
1313
|
+
iss.path.unshift(path20);
|
|
1314
1314
|
return iss;
|
|
1315
1315
|
});
|
|
1316
1316
|
}
|
|
@@ -1493,7 +1493,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1493
1493
|
}
|
|
1494
1494
|
function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
1495
1495
|
const result = { errors: [] };
|
|
1496
|
-
const processError = (error49,
|
|
1496
|
+
const processError = (error49, path20 = []) => {
|
|
1497
1497
|
var _a2, _b;
|
|
1498
1498
|
for (const issue2 of error49.issues) {
|
|
1499
1499
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -1503,7 +1503,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1503
1503
|
} else if (issue2.code === "invalid_element") {
|
|
1504
1504
|
processError({ issues: issue2.issues }, issue2.path);
|
|
1505
1505
|
} else {
|
|
1506
|
-
const fullpath = [...
|
|
1506
|
+
const fullpath = [...path20, ...issue2.path];
|
|
1507
1507
|
if (fullpath.length === 0) {
|
|
1508
1508
|
result.errors.push(mapper(issue2));
|
|
1509
1509
|
continue;
|
|
@@ -1535,8 +1535,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1535
1535
|
}
|
|
1536
1536
|
function toDotPath(_path) {
|
|
1537
1537
|
const segs = [];
|
|
1538
|
-
const
|
|
1539
|
-
for (const seg of
|
|
1538
|
+
const path20 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1539
|
+
for (const seg of path20) {
|
|
1540
1540
|
if (typeof seg === "number")
|
|
1541
1541
|
segs.push(`[${seg}]`);
|
|
1542
1542
|
else if (typeof seg === "symbol")
|
|
@@ -13513,13 +13513,13 @@ function resolveRef(ref, ctx) {
|
|
|
13513
13513
|
if (!ref.startsWith("#")) {
|
|
13514
13514
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
13515
13515
|
}
|
|
13516
|
-
const
|
|
13517
|
-
if (
|
|
13516
|
+
const path20 = ref.slice(1).split("/").filter(Boolean);
|
|
13517
|
+
if (path20.length === 0) {
|
|
13518
13518
|
return ctx.rootSchema;
|
|
13519
13519
|
}
|
|
13520
13520
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
13521
|
-
if (
|
|
13522
|
-
const key =
|
|
13521
|
+
if (path20[0] === defsKey) {
|
|
13522
|
+
const key = path20[1];
|
|
13523
13523
|
if (!key || !ctx.defs[key]) {
|
|
13524
13524
|
throw new Error(`Reference not found: ${ref}`);
|
|
13525
13525
|
}
|
|
@@ -14124,7 +14124,7 @@ function collectGarMetrics(store) {
|
|
|
14124
14124
|
);
|
|
14125
14125
|
const openQuestions = store.list({ type: "question", status: "open" });
|
|
14126
14126
|
const riskItems = allDocs.filter(
|
|
14127
|
-
(d) => d.frontmatter.tags?.includes("risk")
|
|
14127
|
+
(d) => d.frontmatter.tags?.includes("risk") && d.frontmatter.status !== "done" && d.frontmatter.status !== "closed"
|
|
14128
14128
|
);
|
|
14129
14129
|
const unownedActions = openActions.filter((d) => !d.frontmatter.owner);
|
|
14130
14130
|
const total = allActions.length;
|
|
@@ -14525,7 +14525,7 @@ function createReportTools(store) {
|
|
|
14525
14525
|
async () => {
|
|
14526
14526
|
const allDocs = store.list();
|
|
14527
14527
|
const taggedRisks = allDocs.filter(
|
|
14528
|
-
(d) => d.frontmatter.tags?.includes("risk")
|
|
14528
|
+
(d) => d.frontmatter.tags?.includes("risk") && d.frontmatter.status !== "done" && d.frontmatter.status !== "closed"
|
|
14529
14529
|
);
|
|
14530
14530
|
const highPriorityActions = store.list({ type: "action", status: "open" }).filter((d) => d.frontmatter.priority === "high");
|
|
14531
14531
|
const unresolvedQuestions = store.list({ type: "question", status: "open" });
|
|
@@ -14896,7 +14896,8 @@ function createFeatureTools(store) {
|
|
|
14896
14896
|
status: external_exports.enum(["draft", "approved", "deferred", "done"]).optional().describe("New status"),
|
|
14897
14897
|
content: external_exports.string().optional().describe("New content"),
|
|
14898
14898
|
owner: external_exports.string().optional().describe("New owner"),
|
|
14899
|
-
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority")
|
|
14899
|
+
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
|
|
14900
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
14900
14901
|
},
|
|
14901
14902
|
async (args) => {
|
|
14902
14903
|
const { id, content, ...updates } = args;
|
|
@@ -15053,7 +15054,8 @@ function createEpicTools(store) {
|
|
|
15053
15054
|
content: external_exports.string().optional().describe("New content"),
|
|
15054
15055
|
owner: external_exports.string().optional().describe("New owner"),
|
|
15055
15056
|
targetDate: external_exports.string().optional().describe("New target date"),
|
|
15056
|
-
estimatedEffort: external_exports.string().optional().describe("New estimated effort")
|
|
15057
|
+
estimatedEffort: external_exports.string().optional().describe("New estimated effort"),
|
|
15058
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
15057
15059
|
},
|
|
15058
15060
|
async (args) => {
|
|
15059
15061
|
const { id, content, ...updates } = args;
|
|
@@ -16625,6 +16627,56 @@ function getPluginPromptFragment(plugin, personaId) {
|
|
|
16625
16627
|
return plugin.promptFragments[personaId] ?? plugin.promptFragments["*"];
|
|
16626
16628
|
}
|
|
16627
16629
|
|
|
16630
|
+
// src/templates/claude-md.ts
|
|
16631
|
+
function getDefaultClaudeMdContent(projectName) {
|
|
16632
|
+
return `# Marvin \u2014 Project Instructions for "${projectName}"
|
|
16633
|
+
|
|
16634
|
+
You are **Marvin**, an AI-powered product development assistant.
|
|
16635
|
+
You operate as one of three personas \u2014 stay in role and suggest switching when a question falls outside your scope.
|
|
16636
|
+
|
|
16637
|
+
## Personas
|
|
16638
|
+
|
|
16639
|
+
| Persona | Short | Focus |
|
|
16640
|
+
|---------|-------|-------|
|
|
16641
|
+
| Product Owner | po | Vision, backlog, requirements, features, acceptance criteria |
|
|
16642
|
+
| Delivery Manager | dm | Planning, risks, actions, timelines, sprints, status |
|
|
16643
|
+
| Tech Lead | tl | Architecture, trade-offs, technical decisions, code quality |
|
|
16644
|
+
|
|
16645
|
+
## Proactive Governance
|
|
16646
|
+
|
|
16647
|
+
When conversation implies a commitment, risk, or open question, **suggest creating the matching artifact**:
|
|
16648
|
+
- A decision was made \u2192 offer to create a **Decision (D-xxx)**
|
|
16649
|
+
- Someone committed to a task \u2192 offer an **Action (A-xxx)** with owner and due date
|
|
16650
|
+
- An unanswered question surfaced \u2192 offer a **Question (Q-xxx)**
|
|
16651
|
+
- A new capability is discussed \u2192 offer a **Feature (F-xxx)**
|
|
16652
|
+
- Implementation scope is agreed \u2192 offer an **Epic (E-xxx)** linked to a feature
|
|
16653
|
+
- Work is being time-boxed \u2192 offer a **Sprint (SP-xxx)**
|
|
16654
|
+
|
|
16655
|
+
## Insights
|
|
16656
|
+
|
|
16657
|
+
Proactively flag:
|
|
16658
|
+
- Overdue actions or unresolved questions
|
|
16659
|
+
- Decisions without rationale or linked features
|
|
16660
|
+
- Features without linked epics
|
|
16661
|
+
- Risks mentioned but not tracked
|
|
16662
|
+
- When a risk is resolved \u2192 remove the "risk" tag and add "risk-mitigated"
|
|
16663
|
+
|
|
16664
|
+
## Tool Usage
|
|
16665
|
+
|
|
16666
|
+
- **Search before creating** \u2014 avoid duplicate artifacts
|
|
16667
|
+
- **Reference IDs** (e.g. D-001, A-003) when discussing existing items
|
|
16668
|
+
- **Link artifacts** \u2014 epics to features, actions to decisions, etc.
|
|
16669
|
+
- Use \`search_documents\` to find related context before answering
|
|
16670
|
+
|
|
16671
|
+
## Communication Style
|
|
16672
|
+
|
|
16673
|
+
- Be concise and structured
|
|
16674
|
+
- State assumptions explicitly
|
|
16675
|
+
- Use bullet points and tables where they aid clarity
|
|
16676
|
+
- When uncertain, ask a clarifying question rather than guessing
|
|
16677
|
+
`;
|
|
16678
|
+
}
|
|
16679
|
+
|
|
16628
16680
|
// src/cli/commands/init.ts
|
|
16629
16681
|
async function initCommand() {
|
|
16630
16682
|
const cwd = process.cwd();
|
|
@@ -16681,11 +16733,17 @@ async function initCommand() {
|
|
|
16681
16733
|
YAML3.stringify(config2),
|
|
16682
16734
|
"utf-8"
|
|
16683
16735
|
);
|
|
16736
|
+
fs4.writeFileSync(
|
|
16737
|
+
path4.join(marvinDir, "CLAUDE.md"),
|
|
16738
|
+
getDefaultClaudeMdContent(projectName),
|
|
16739
|
+
"utf-8"
|
|
16740
|
+
);
|
|
16684
16741
|
console.log(chalk.green(`
|
|
16685
16742
|
Initialized Marvin project "${projectName}" in ${cwd}`));
|
|
16686
16743
|
console.log(chalk.dim(`Methodology: ${plugin?.name ?? methodology}`));
|
|
16687
16744
|
console.log(chalk.dim("\nCreated:"));
|
|
16688
16745
|
console.log(chalk.dim(" .marvin/config.yaml"));
|
|
16746
|
+
console.log(chalk.dim(" .marvin/CLAUDE.md"));
|
|
16689
16747
|
console.log(chalk.dim(" .marvin/docs/decisions/"));
|
|
16690
16748
|
console.log(chalk.dim(" .marvin/docs/actions/"));
|
|
16691
16749
|
console.log(chalk.dim(" .marvin/docs/questions/"));
|
|
@@ -16888,8 +16946,8 @@ function resolvePersonaId(input4) {
|
|
|
16888
16946
|
}
|
|
16889
16947
|
|
|
16890
16948
|
// src/agent/session.ts
|
|
16891
|
-
import * as
|
|
16892
|
-
import * as
|
|
16949
|
+
import * as fs10 from "fs";
|
|
16950
|
+
import * as path10 from "path";
|
|
16893
16951
|
import * as readline from "readline";
|
|
16894
16952
|
import chalk2 from "chalk";
|
|
16895
16953
|
import ora from "ora";
|
|
@@ -16898,9 +16956,24 @@ import {
|
|
|
16898
16956
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
16899
16957
|
|
|
16900
16958
|
// src/personas/prompt-builder.ts
|
|
16901
|
-
|
|
16959
|
+
import * as fs5 from "fs";
|
|
16960
|
+
import * as path5 from "path";
|
|
16961
|
+
function buildSystemPrompt(persona, projectConfig, pluginPromptFragment, skillPromptFragment, marvinDir) {
|
|
16902
16962
|
const parts = [];
|
|
16903
16963
|
parts.push(persona.systemPrompt);
|
|
16964
|
+
if (marvinDir) {
|
|
16965
|
+
const claudeMdPath = path5.join(marvinDir, "CLAUDE.md");
|
|
16966
|
+
try {
|
|
16967
|
+
const content = fs5.readFileSync(claudeMdPath, "utf-8").trim();
|
|
16968
|
+
if (content) {
|
|
16969
|
+
parts.push(`
|
|
16970
|
+
## Project Instructions
|
|
16971
|
+
${content}
|
|
16972
|
+
`);
|
|
16973
|
+
}
|
|
16974
|
+
} catch {
|
|
16975
|
+
}
|
|
16976
|
+
}
|
|
16904
16977
|
parts.push(`
|
|
16905
16978
|
## Project Context
|
|
16906
16979
|
- **Project Name:** ${projectConfig.name}
|
|
@@ -16947,8 +17020,8 @@ ${projectConfig.personas[persona.id].extraInstructions}
|
|
|
16947
17020
|
}
|
|
16948
17021
|
|
|
16949
17022
|
// src/storage/store.ts
|
|
16950
|
-
import * as
|
|
16951
|
-
import * as
|
|
17023
|
+
import * as fs6 from "fs";
|
|
17024
|
+
import * as path6 from "path";
|
|
16952
17025
|
|
|
16953
17026
|
// src/storage/document.ts
|
|
16954
17027
|
import matter from "gray-matter";
|
|
@@ -16986,7 +17059,7 @@ var DocumentStore = class {
|
|
|
16986
17059
|
typeDirs;
|
|
16987
17060
|
idPrefixes;
|
|
16988
17061
|
constructor(marvinDir, registrations) {
|
|
16989
|
-
this.docsDir =
|
|
17062
|
+
this.docsDir = path6.join(marvinDir, "docs");
|
|
16990
17063
|
this.typeDirs = { ...CORE_TYPE_DIRS };
|
|
16991
17064
|
this.idPrefixes = { ...CORE_ID_PREFIXES };
|
|
16992
17065
|
for (const reg of registrations ?? []) {
|
|
@@ -17001,12 +17074,12 @@ var DocumentStore = class {
|
|
|
17001
17074
|
buildIndex() {
|
|
17002
17075
|
this.index.clear();
|
|
17003
17076
|
for (const type of Object.keys(this.typeDirs)) {
|
|
17004
|
-
const dir =
|
|
17005
|
-
if (!
|
|
17006
|
-
const files =
|
|
17077
|
+
const dir = path6.join(this.docsDir, this.typeDirs[type]);
|
|
17078
|
+
if (!fs6.existsSync(dir)) continue;
|
|
17079
|
+
const files = fs6.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
17007
17080
|
for (const file2 of files) {
|
|
17008
|
-
const filePath =
|
|
17009
|
-
const raw =
|
|
17081
|
+
const filePath = path6.join(dir, file2);
|
|
17082
|
+
const raw = fs6.readFileSync(filePath, "utf-8");
|
|
17010
17083
|
const doc = parseDocument(raw, filePath);
|
|
17011
17084
|
if (doc.frontmatter.id) {
|
|
17012
17085
|
if (this.index.has(doc.frontmatter.id)) {
|
|
@@ -17025,12 +17098,12 @@ var DocumentStore = class {
|
|
|
17025
17098
|
for (const type of types) {
|
|
17026
17099
|
const dirName = this.typeDirs[type];
|
|
17027
17100
|
if (!dirName) continue;
|
|
17028
|
-
const dir =
|
|
17029
|
-
if (!
|
|
17030
|
-
const files =
|
|
17101
|
+
const dir = path6.join(this.docsDir, dirName);
|
|
17102
|
+
if (!fs6.existsSync(dir)) continue;
|
|
17103
|
+
const files = fs6.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
17031
17104
|
for (const file2 of files) {
|
|
17032
|
-
const filePath =
|
|
17033
|
-
const raw =
|
|
17105
|
+
const filePath = path6.join(dir, file2);
|
|
17106
|
+
const raw = fs6.readFileSync(filePath, "utf-8");
|
|
17034
17107
|
const doc = parseDocument(raw, filePath);
|
|
17035
17108
|
if (query7?.status && doc.frontmatter.status !== query7.status) continue;
|
|
17036
17109
|
if (query7?.owner && doc.frontmatter.owner !== query7.owner) continue;
|
|
@@ -17043,12 +17116,12 @@ var DocumentStore = class {
|
|
|
17043
17116
|
}
|
|
17044
17117
|
get(id) {
|
|
17045
17118
|
for (const type of Object.keys(this.typeDirs)) {
|
|
17046
|
-
const dir =
|
|
17047
|
-
if (!
|
|
17048
|
-
const files =
|
|
17119
|
+
const dir = path6.join(this.docsDir, this.typeDirs[type]);
|
|
17120
|
+
if (!fs6.existsSync(dir)) continue;
|
|
17121
|
+
const files = fs6.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
17049
17122
|
for (const file2 of files) {
|
|
17050
|
-
const filePath =
|
|
17051
|
-
const raw =
|
|
17123
|
+
const filePath = path6.join(dir, file2);
|
|
17124
|
+
const raw = fs6.readFileSync(filePath, "utf-8");
|
|
17052
17125
|
const doc = parseDocument(raw, filePath);
|
|
17053
17126
|
if (doc.frontmatter.id === id) return doc;
|
|
17054
17127
|
}
|
|
@@ -17062,8 +17135,8 @@ var DocumentStore = class {
|
|
|
17062
17135
|
if (!dirName) {
|
|
17063
17136
|
throw new Error(`Unknown document type: ${type}`);
|
|
17064
17137
|
}
|
|
17065
|
-
const dir =
|
|
17066
|
-
|
|
17138
|
+
const dir = path6.join(this.docsDir, dirName);
|
|
17139
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
17067
17140
|
const cleaned = Object.fromEntries(
|
|
17068
17141
|
Object.entries(frontmatter).filter(([, v]) => v !== void 0)
|
|
17069
17142
|
);
|
|
@@ -17077,13 +17150,13 @@ var DocumentStore = class {
|
|
|
17077
17150
|
...cleaned
|
|
17078
17151
|
};
|
|
17079
17152
|
const fileName = type === "meeting" ? `${cleaned.date?.slice(0, 10) ?? now.slice(0, 10)}-${slugify2(fullFrontmatter.title)}.md` : `${id}.md`;
|
|
17080
|
-
const filePath =
|
|
17153
|
+
const filePath = path6.join(dir, fileName);
|
|
17081
17154
|
const doc = {
|
|
17082
17155
|
frontmatter: fullFrontmatter,
|
|
17083
17156
|
content,
|
|
17084
17157
|
filePath
|
|
17085
17158
|
};
|
|
17086
|
-
|
|
17159
|
+
fs6.writeFileSync(filePath, serializeDocument(doc), "utf-8");
|
|
17087
17160
|
this.index.set(id, fullFrontmatter);
|
|
17088
17161
|
return doc;
|
|
17089
17162
|
}
|
|
@@ -17098,12 +17171,12 @@ var DocumentStore = class {
|
|
|
17098
17171
|
`Document ${frontmatter.id} already exists. Resolve conflicts before importing.`
|
|
17099
17172
|
);
|
|
17100
17173
|
}
|
|
17101
|
-
const dir =
|
|
17102
|
-
|
|
17174
|
+
const dir = path6.join(this.docsDir, dirName);
|
|
17175
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
17103
17176
|
const fileName = type === "meeting" ? `${frontmatter.date?.slice(0, 10) ?? frontmatter.created.slice(0, 10)}-${slugify2(frontmatter.title)}.md` : `${frontmatter.id}.md`;
|
|
17104
|
-
const filePath =
|
|
17177
|
+
const filePath = path6.join(dir, fileName);
|
|
17105
17178
|
const doc = { frontmatter, content, filePath };
|
|
17106
|
-
|
|
17179
|
+
fs6.writeFileSync(filePath, serializeDocument(doc), "utf-8");
|
|
17107
17180
|
this.index.set(frontmatter.id, frontmatter);
|
|
17108
17181
|
return doc;
|
|
17109
17182
|
}
|
|
@@ -17125,7 +17198,7 @@ var DocumentStore = class {
|
|
|
17125
17198
|
content: content ?? existing.content,
|
|
17126
17199
|
filePath: existing.filePath
|
|
17127
17200
|
};
|
|
17128
|
-
|
|
17201
|
+
fs6.writeFileSync(existing.filePath, serializeDocument(doc), "utf-8");
|
|
17129
17202
|
this.index.set(id, updatedFrontmatter);
|
|
17130
17203
|
return doc;
|
|
17131
17204
|
}
|
|
@@ -17135,14 +17208,14 @@ var DocumentStore = class {
|
|
|
17135
17208
|
throw new Error(`Unknown document type: ${type}`);
|
|
17136
17209
|
}
|
|
17137
17210
|
const dirName = this.typeDirs[type];
|
|
17138
|
-
const dir =
|
|
17139
|
-
if (!
|
|
17211
|
+
const dir = path6.join(this.docsDir, dirName);
|
|
17212
|
+
if (!fs6.existsSync(dir)) return `${prefix}-001`;
|
|
17140
17213
|
const idPattern = new RegExp(`^${prefix}-(\\d+)$`);
|
|
17141
|
-
const files =
|
|
17214
|
+
const files = fs6.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
17142
17215
|
let maxNum = 0;
|
|
17143
17216
|
for (const file2 of files) {
|
|
17144
|
-
const filePath =
|
|
17145
|
-
const raw =
|
|
17217
|
+
const filePath = path6.join(dir, file2);
|
|
17218
|
+
const raw = fs6.readFileSync(filePath, "utf-8");
|
|
17146
17219
|
const doc = parseDocument(raw, filePath);
|
|
17147
17220
|
const match = doc.frontmatter.id?.match(idPattern);
|
|
17148
17221
|
if (match) {
|
|
@@ -17155,12 +17228,12 @@ var DocumentStore = class {
|
|
|
17155
17228
|
counts() {
|
|
17156
17229
|
const result = {};
|
|
17157
17230
|
for (const type of Object.keys(this.typeDirs)) {
|
|
17158
|
-
const dir =
|
|
17159
|
-
if (!
|
|
17231
|
+
const dir = path6.join(this.docsDir, this.typeDirs[type]);
|
|
17232
|
+
if (!fs6.existsSync(dir)) {
|
|
17160
17233
|
result[type] = 0;
|
|
17161
17234
|
continue;
|
|
17162
17235
|
}
|
|
17163
|
-
result[type] =
|
|
17236
|
+
result[type] = fs6.readdirSync(dir).filter((f) => f.endsWith(".md")).length;
|
|
17164
17237
|
}
|
|
17165
17238
|
return result;
|
|
17166
17239
|
}
|
|
@@ -17170,13 +17243,13 @@ function slugify2(text) {
|
|
|
17170
17243
|
}
|
|
17171
17244
|
|
|
17172
17245
|
// src/storage/session-store.ts
|
|
17173
|
-
import * as
|
|
17174
|
-
import * as
|
|
17246
|
+
import * as fs7 from "fs";
|
|
17247
|
+
import * as path7 from "path";
|
|
17175
17248
|
import * as YAML4 from "yaml";
|
|
17176
17249
|
var SessionStore = class {
|
|
17177
17250
|
filePath;
|
|
17178
17251
|
constructor(marvinDir) {
|
|
17179
|
-
this.filePath =
|
|
17252
|
+
this.filePath = path7.join(marvinDir, "sessions.yaml");
|
|
17180
17253
|
}
|
|
17181
17254
|
list() {
|
|
17182
17255
|
const entries = this.load();
|
|
@@ -17217,9 +17290,9 @@ var SessionStore = class {
|
|
|
17217
17290
|
this.write(entries);
|
|
17218
17291
|
}
|
|
17219
17292
|
load() {
|
|
17220
|
-
if (!
|
|
17293
|
+
if (!fs7.existsSync(this.filePath)) return [];
|
|
17221
17294
|
try {
|
|
17222
|
-
const raw =
|
|
17295
|
+
const raw = fs7.readFileSync(this.filePath, "utf-8");
|
|
17223
17296
|
const parsed = YAML4.parse(raw);
|
|
17224
17297
|
if (!Array.isArray(parsed)) return [];
|
|
17225
17298
|
return parsed;
|
|
@@ -17228,11 +17301,11 @@ var SessionStore = class {
|
|
|
17228
17301
|
}
|
|
17229
17302
|
}
|
|
17230
17303
|
write(entries) {
|
|
17231
|
-
const dir =
|
|
17232
|
-
if (!
|
|
17233
|
-
|
|
17304
|
+
const dir = path7.dirname(this.filePath);
|
|
17305
|
+
if (!fs7.existsSync(dir)) {
|
|
17306
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
17234
17307
|
}
|
|
17235
|
-
|
|
17308
|
+
fs7.writeFileSync(this.filePath, YAML4.stringify(entries), "utf-8");
|
|
17236
17309
|
}
|
|
17237
17310
|
};
|
|
17238
17311
|
|
|
@@ -17329,7 +17402,8 @@ function createDecisionTools(store) {
|
|
|
17329
17402
|
title: external_exports.string().optional().describe("New title"),
|
|
17330
17403
|
status: external_exports.string().optional().describe("New status"),
|
|
17331
17404
|
content: external_exports.string().optional().describe("New content"),
|
|
17332
|
-
owner: external_exports.string().optional().describe("New owner")
|
|
17405
|
+
owner: external_exports.string().optional().describe("New owner"),
|
|
17406
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
17333
17407
|
},
|
|
17334
17408
|
async (args) => {
|
|
17335
17409
|
const { id, content, ...updates } = args;
|
|
@@ -17480,11 +17554,21 @@ function createActionTools(store) {
|
|
|
17480
17554
|
owner: external_exports.string().optional().describe("New owner"),
|
|
17481
17555
|
priority: external_exports.string().optional().describe("New priority"),
|
|
17482
17556
|
dueDate: external_exports.string().optional().describe("Due date in ISO format (e.g. '2026-03-15')"),
|
|
17557
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace all tags. When provided with sprints, sprint tags are merged into this array."),
|
|
17483
17558
|
sprints: external_exports.array(external_exports.string()).optional().describe("Sprint IDs to assign (replaces existing sprint tags). E.g. ['SP-001'].")
|
|
17484
17559
|
},
|
|
17485
17560
|
async (args) => {
|
|
17486
|
-
const { id, content, sprints, ...updates } = args;
|
|
17487
|
-
if (
|
|
17561
|
+
const { id, content, sprints, tags, ...updates } = args;
|
|
17562
|
+
if (tags !== void 0) {
|
|
17563
|
+
const merged = [...tags];
|
|
17564
|
+
if (sprints) {
|
|
17565
|
+
for (const s of sprints) {
|
|
17566
|
+
const tag = `sprint:${s}`;
|
|
17567
|
+
if (!merged.includes(tag)) merged.push(tag);
|
|
17568
|
+
}
|
|
17569
|
+
}
|
|
17570
|
+
updates.tags = merged;
|
|
17571
|
+
} else if (sprints !== void 0) {
|
|
17488
17572
|
const existing = store.get(id);
|
|
17489
17573
|
if (!existing) {
|
|
17490
17574
|
return {
|
|
@@ -17631,7 +17715,8 @@ function createQuestionTools(store) {
|
|
|
17631
17715
|
title: external_exports.string().optional().describe("New title"),
|
|
17632
17716
|
status: external_exports.string().optional().describe("New status (e.g. 'answered')"),
|
|
17633
17717
|
content: external_exports.string().optional().describe("Updated content / answer"),
|
|
17634
|
-
owner: external_exports.string().optional().describe("New owner")
|
|
17718
|
+
owner: external_exports.string().optional().describe("New owner"),
|
|
17719
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
17635
17720
|
},
|
|
17636
17721
|
async (args) => {
|
|
17637
17722
|
const { id, content, ...updates } = args;
|
|
@@ -19271,8 +19356,8 @@ import * as http from "http";
|
|
|
19271
19356
|
import { exec } from "child_process";
|
|
19272
19357
|
|
|
19273
19358
|
// src/skills/registry.ts
|
|
19274
|
-
import * as
|
|
19275
|
-
import * as
|
|
19359
|
+
import * as fs8 from "fs";
|
|
19360
|
+
import * as path8 from "path";
|
|
19276
19361
|
import { fileURLToPath } from "url";
|
|
19277
19362
|
import * as YAML5 from "yaml";
|
|
19278
19363
|
import matter2 from "gray-matter";
|
|
@@ -19325,8 +19410,8 @@ var JiraClient = class {
|
|
|
19325
19410
|
this.baseUrl = `https://${config2.host}/rest/api/2`;
|
|
19326
19411
|
this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
|
|
19327
19412
|
}
|
|
19328
|
-
async request(
|
|
19329
|
-
const url2 = `${this.baseUrl}${
|
|
19413
|
+
async request(path20, method = "GET", body) {
|
|
19414
|
+
const url2 = `${this.baseUrl}${path20}`;
|
|
19330
19415
|
const headers = {
|
|
19331
19416
|
Authorization: this.authHeader,
|
|
19332
19417
|
"Content-Type": "application/json",
|
|
@@ -19340,7 +19425,7 @@ var JiraClient = class {
|
|
|
19340
19425
|
if (!response.ok) {
|
|
19341
19426
|
const text = await response.text().catch(() => "");
|
|
19342
19427
|
throw new Error(
|
|
19343
|
-
`Jira API error ${response.status} ${method} ${
|
|
19428
|
+
`Jira API error ${response.status} ${method} ${path20}: ${text}`
|
|
19344
19429
|
);
|
|
19345
19430
|
}
|
|
19346
19431
|
if (response.status === 204) return void 0;
|
|
@@ -19820,13 +19905,13 @@ var GOVERNANCE_TOOL_NAMES = [
|
|
|
19820
19905
|
];
|
|
19821
19906
|
function getBuiltinSkillsDir() {
|
|
19822
19907
|
const thisFile = fileURLToPath(import.meta.url);
|
|
19823
|
-
return
|
|
19908
|
+
return path8.join(path8.dirname(thisFile), "builtin");
|
|
19824
19909
|
}
|
|
19825
19910
|
function loadSkillFromDirectory(dirPath) {
|
|
19826
|
-
const skillMdPath =
|
|
19827
|
-
if (!
|
|
19911
|
+
const skillMdPath = path8.join(dirPath, "SKILL.md");
|
|
19912
|
+
if (!fs8.existsSync(skillMdPath)) return void 0;
|
|
19828
19913
|
try {
|
|
19829
|
-
const raw =
|
|
19914
|
+
const raw = fs8.readFileSync(skillMdPath, "utf-8");
|
|
19830
19915
|
const { data, content } = matter2(raw);
|
|
19831
19916
|
if (!data.name || !data.description) return void 0;
|
|
19832
19917
|
const metadata = data.metadata ?? {};
|
|
@@ -19837,13 +19922,13 @@ function loadSkillFromDirectory(dirPath) {
|
|
|
19837
19922
|
if (wildcardPrompt) {
|
|
19838
19923
|
promptFragments["*"] = wildcardPrompt;
|
|
19839
19924
|
}
|
|
19840
|
-
const personasDir =
|
|
19841
|
-
if (
|
|
19925
|
+
const personasDir = path8.join(dirPath, "personas");
|
|
19926
|
+
if (fs8.existsSync(personasDir)) {
|
|
19842
19927
|
try {
|
|
19843
|
-
for (const file2 of
|
|
19928
|
+
for (const file2 of fs8.readdirSync(personasDir)) {
|
|
19844
19929
|
if (!file2.endsWith(".md")) continue;
|
|
19845
19930
|
const personaId = file2.replace(/\.md$/, "");
|
|
19846
|
-
const personaPrompt =
|
|
19931
|
+
const personaPrompt = fs8.readFileSync(path8.join(personasDir, file2), "utf-8").trim();
|
|
19847
19932
|
if (personaPrompt) {
|
|
19848
19933
|
promptFragments[personaId] = personaPrompt;
|
|
19849
19934
|
}
|
|
@@ -19852,10 +19937,10 @@ function loadSkillFromDirectory(dirPath) {
|
|
|
19852
19937
|
}
|
|
19853
19938
|
}
|
|
19854
19939
|
let actions;
|
|
19855
|
-
const actionsPath =
|
|
19856
|
-
if (
|
|
19940
|
+
const actionsPath = path8.join(dirPath, "actions.yaml");
|
|
19941
|
+
if (fs8.existsSync(actionsPath)) {
|
|
19857
19942
|
try {
|
|
19858
|
-
const actionsRaw =
|
|
19943
|
+
const actionsRaw = fs8.readFileSync(actionsPath, "utf-8");
|
|
19859
19944
|
actions = YAML5.parse(actionsRaw);
|
|
19860
19945
|
} catch {
|
|
19861
19946
|
}
|
|
@@ -19882,10 +19967,10 @@ function loadAllSkills(marvinDir) {
|
|
|
19882
19967
|
}
|
|
19883
19968
|
try {
|
|
19884
19969
|
const builtinDir = getBuiltinSkillsDir();
|
|
19885
|
-
if (
|
|
19886
|
-
for (const entry of
|
|
19887
|
-
const entryPath =
|
|
19888
|
-
if (!
|
|
19970
|
+
if (fs8.existsSync(builtinDir)) {
|
|
19971
|
+
for (const entry of fs8.readdirSync(builtinDir)) {
|
|
19972
|
+
const entryPath = path8.join(builtinDir, entry);
|
|
19973
|
+
if (!fs8.statSync(entryPath).isDirectory()) continue;
|
|
19889
19974
|
if (skills.has(entry)) continue;
|
|
19890
19975
|
const skill = loadSkillFromDirectory(entryPath);
|
|
19891
19976
|
if (skill) skills.set(skill.id, skill);
|
|
@@ -19894,18 +19979,18 @@ function loadAllSkills(marvinDir) {
|
|
|
19894
19979
|
} catch {
|
|
19895
19980
|
}
|
|
19896
19981
|
if (marvinDir) {
|
|
19897
|
-
const skillsDir =
|
|
19898
|
-
if (
|
|
19982
|
+
const skillsDir = path8.join(marvinDir, "skills");
|
|
19983
|
+
if (fs8.existsSync(skillsDir)) {
|
|
19899
19984
|
let entries;
|
|
19900
19985
|
try {
|
|
19901
|
-
entries =
|
|
19986
|
+
entries = fs8.readdirSync(skillsDir);
|
|
19902
19987
|
} catch {
|
|
19903
19988
|
entries = [];
|
|
19904
19989
|
}
|
|
19905
19990
|
for (const entry of entries) {
|
|
19906
|
-
const entryPath =
|
|
19991
|
+
const entryPath = path8.join(skillsDir, entry);
|
|
19907
19992
|
try {
|
|
19908
|
-
if (
|
|
19993
|
+
if (fs8.statSync(entryPath).isDirectory()) {
|
|
19909
19994
|
const skill = loadSkillFromDirectory(entryPath);
|
|
19910
19995
|
if (skill) skills.set(skill.id, skill);
|
|
19911
19996
|
continue;
|
|
@@ -19915,7 +20000,7 @@ function loadAllSkills(marvinDir) {
|
|
|
19915
20000
|
}
|
|
19916
20001
|
if (!entry.endsWith(".yaml") && !entry.endsWith(".yml")) continue;
|
|
19917
20002
|
try {
|
|
19918
|
-
const raw =
|
|
20003
|
+
const raw = fs8.readFileSync(entryPath, "utf-8");
|
|
19919
20004
|
const parsed = YAML5.parse(raw);
|
|
19920
20005
|
if (!parsed?.id || !parsed?.name || !parsed?.version) continue;
|
|
19921
20006
|
const skill = {
|
|
@@ -20020,12 +20105,12 @@ function getSkillAgentDefinitions(skillIds, allSkills) {
|
|
|
20020
20105
|
return agents;
|
|
20021
20106
|
}
|
|
20022
20107
|
function migrateYamlToSkillMd(yamlPath, outputDir) {
|
|
20023
|
-
const raw =
|
|
20108
|
+
const raw = fs8.readFileSync(yamlPath, "utf-8");
|
|
20024
20109
|
const parsed = YAML5.parse(raw);
|
|
20025
20110
|
if (!parsed?.id || !parsed?.name) {
|
|
20026
20111
|
throw new Error(`Invalid skill YAML: missing required fields (id, name)`);
|
|
20027
20112
|
}
|
|
20028
|
-
|
|
20113
|
+
fs8.mkdirSync(outputDir, { recursive: true });
|
|
20029
20114
|
const frontmatter = {
|
|
20030
20115
|
name: parsed.id,
|
|
20031
20116
|
description: parsed.description ?? ""
|
|
@@ -20039,15 +20124,15 @@ function migrateYamlToSkillMd(yamlPath, outputDir) {
|
|
|
20039
20124
|
const skillMd = matter2.stringify(wildcardPrompt ? `
|
|
20040
20125
|
${wildcardPrompt}
|
|
20041
20126
|
` : "\n", frontmatter);
|
|
20042
|
-
|
|
20127
|
+
fs8.writeFileSync(path8.join(outputDir, "SKILL.md"), skillMd, "utf-8");
|
|
20043
20128
|
if (promptFragments) {
|
|
20044
20129
|
const personaKeys = Object.keys(promptFragments).filter((k) => k !== "*");
|
|
20045
20130
|
if (personaKeys.length > 0) {
|
|
20046
|
-
const personasDir =
|
|
20047
|
-
|
|
20131
|
+
const personasDir = path8.join(outputDir, "personas");
|
|
20132
|
+
fs8.mkdirSync(personasDir, { recursive: true });
|
|
20048
20133
|
for (const personaId of personaKeys) {
|
|
20049
|
-
|
|
20050
|
-
|
|
20134
|
+
fs8.writeFileSync(
|
|
20135
|
+
path8.join(personasDir, `${personaId}.md`),
|
|
20051
20136
|
`${promptFragments[personaId]}
|
|
20052
20137
|
`,
|
|
20053
20138
|
"utf-8"
|
|
@@ -20057,8 +20142,8 @@ ${wildcardPrompt}
|
|
|
20057
20142
|
}
|
|
20058
20143
|
const actions = parsed.actions;
|
|
20059
20144
|
if (actions && actions.length > 0) {
|
|
20060
|
-
|
|
20061
|
-
|
|
20145
|
+
fs8.writeFileSync(
|
|
20146
|
+
path8.join(outputDir, "actions.yaml"),
|
|
20062
20147
|
YAML5.stringify(actions),
|
|
20063
20148
|
"utf-8"
|
|
20064
20149
|
);
|
|
@@ -20332,8 +20417,8 @@ function slugify3(text) {
|
|
|
20332
20417
|
}
|
|
20333
20418
|
|
|
20334
20419
|
// src/sources/manifest.ts
|
|
20335
|
-
import * as
|
|
20336
|
-
import * as
|
|
20420
|
+
import * as fs9 from "fs";
|
|
20421
|
+
import * as path9 from "path";
|
|
20337
20422
|
import * as crypto from "crypto";
|
|
20338
20423
|
import * as YAML6 from "yaml";
|
|
20339
20424
|
var MANIFEST_FILE = ".manifest.yaml";
|
|
@@ -20346,37 +20431,37 @@ var SourceManifestManager = class {
|
|
|
20346
20431
|
manifestPath;
|
|
20347
20432
|
sourcesDir;
|
|
20348
20433
|
constructor(marvinDir) {
|
|
20349
|
-
this.sourcesDir =
|
|
20350
|
-
this.manifestPath =
|
|
20434
|
+
this.sourcesDir = path9.join(marvinDir, "sources");
|
|
20435
|
+
this.manifestPath = path9.join(this.sourcesDir, MANIFEST_FILE);
|
|
20351
20436
|
this.manifest = this.load();
|
|
20352
20437
|
}
|
|
20353
20438
|
load() {
|
|
20354
|
-
if (!
|
|
20439
|
+
if (!fs9.existsSync(this.manifestPath)) {
|
|
20355
20440
|
return emptyManifest();
|
|
20356
20441
|
}
|
|
20357
|
-
const raw =
|
|
20442
|
+
const raw = fs9.readFileSync(this.manifestPath, "utf-8");
|
|
20358
20443
|
const parsed = YAML6.parse(raw);
|
|
20359
20444
|
return parsed ?? emptyManifest();
|
|
20360
20445
|
}
|
|
20361
20446
|
save() {
|
|
20362
|
-
|
|
20363
|
-
|
|
20447
|
+
fs9.mkdirSync(this.sourcesDir, { recursive: true });
|
|
20448
|
+
fs9.writeFileSync(this.manifestPath, YAML6.stringify(this.manifest), "utf-8");
|
|
20364
20449
|
}
|
|
20365
20450
|
scan() {
|
|
20366
20451
|
const added = [];
|
|
20367
20452
|
const changed = [];
|
|
20368
20453
|
const removed = [];
|
|
20369
|
-
if (!
|
|
20454
|
+
if (!fs9.existsSync(this.sourcesDir)) {
|
|
20370
20455
|
return { added, changed, removed };
|
|
20371
20456
|
}
|
|
20372
20457
|
const onDisk = new Set(
|
|
20373
|
-
|
|
20374
|
-
const ext =
|
|
20458
|
+
fs9.readdirSync(this.sourcesDir).filter((f) => {
|
|
20459
|
+
const ext = path9.extname(f).toLowerCase();
|
|
20375
20460
|
return SOURCE_EXTENSIONS.includes(ext);
|
|
20376
20461
|
})
|
|
20377
20462
|
);
|
|
20378
20463
|
for (const fileName of onDisk) {
|
|
20379
|
-
const filePath =
|
|
20464
|
+
const filePath = path9.join(this.sourcesDir, fileName);
|
|
20380
20465
|
const hash2 = this.hashFile(filePath);
|
|
20381
20466
|
const existing = this.manifest.files[fileName];
|
|
20382
20467
|
if (!existing) {
|
|
@@ -20439,7 +20524,7 @@ var SourceManifestManager = class {
|
|
|
20439
20524
|
this.save();
|
|
20440
20525
|
}
|
|
20441
20526
|
hashFile(filePath) {
|
|
20442
|
-
const content =
|
|
20527
|
+
const content = fs9.readFileSync(filePath);
|
|
20443
20528
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
20444
20529
|
}
|
|
20445
20530
|
};
|
|
@@ -20454,8 +20539,8 @@ async function startSession(options) {
|
|
|
20454
20539
|
const skillRegistrations = collectSkillRegistrations(skillIds, allSkills);
|
|
20455
20540
|
const store = new DocumentStore(marvinDir, [...pluginRegistrations, ...skillRegistrations]);
|
|
20456
20541
|
const sessionStore = new SessionStore(marvinDir);
|
|
20457
|
-
const sourcesDir =
|
|
20458
|
-
const hasSourcesDir =
|
|
20542
|
+
const sourcesDir = path10.join(marvinDir, "sources");
|
|
20543
|
+
const hasSourcesDir = fs10.existsSync(sourcesDir);
|
|
20459
20544
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
20460
20545
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
20461
20546
|
const pluginPromptFragment = plugin ? getPluginPromptFragment(plugin, persona.id) : void 0;
|
|
@@ -20478,7 +20563,7 @@ async function startSession(options) {
|
|
|
20478
20563
|
projectName: config2.project.name,
|
|
20479
20564
|
navGroups
|
|
20480
20565
|
});
|
|
20481
|
-
const systemPrompt = buildSystemPrompt(persona, config2.project, pluginPromptFragment, skillPromptFragment);
|
|
20566
|
+
const systemPrompt = buildSystemPrompt(persona, config2.project, pluginPromptFragment, skillPromptFragment, marvinDir);
|
|
20482
20567
|
let existingSession;
|
|
20483
20568
|
if (options.sessionName) {
|
|
20484
20569
|
existingSession = sessionStore.get(options.sessionName);
|
|
@@ -20931,13 +21016,13 @@ async function setApiKey() {
|
|
|
20931
21016
|
}
|
|
20932
21017
|
|
|
20933
21018
|
// src/cli/commands/ingest.ts
|
|
20934
|
-
import * as
|
|
20935
|
-
import * as
|
|
21019
|
+
import * as fs12 from "fs";
|
|
21020
|
+
import * as path12 from "path";
|
|
20936
21021
|
import chalk8 from "chalk";
|
|
20937
21022
|
|
|
20938
21023
|
// src/sources/ingest.ts
|
|
20939
|
-
import * as
|
|
20940
|
-
import * as
|
|
21024
|
+
import * as fs11 from "fs";
|
|
21025
|
+
import * as path11 from "path";
|
|
20941
21026
|
import chalk7 from "chalk";
|
|
20942
21027
|
import ora2 from "ora";
|
|
20943
21028
|
import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -21040,15 +21125,15 @@ async function ingestFile(options) {
|
|
|
21040
21125
|
const persona = getPersona(personaId);
|
|
21041
21126
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21042
21127
|
const sourcesDir = manifest.sourcesDir;
|
|
21043
|
-
const filePath =
|
|
21044
|
-
if (!
|
|
21128
|
+
const filePath = path11.join(sourcesDir, fileName);
|
|
21129
|
+
if (!fs11.existsSync(filePath)) {
|
|
21045
21130
|
throw new Error(`Source file not found: ${filePath}`);
|
|
21046
21131
|
}
|
|
21047
|
-
const ext =
|
|
21132
|
+
const ext = path11.extname(fileName).toLowerCase();
|
|
21048
21133
|
const isPdf = ext === ".pdf";
|
|
21049
21134
|
let fileContent = null;
|
|
21050
21135
|
if (!isPdf) {
|
|
21051
|
-
fileContent =
|
|
21136
|
+
fileContent = fs11.readFileSync(filePath, "utf-8");
|
|
21052
21137
|
}
|
|
21053
21138
|
const store = new DocumentStore(marvinDir);
|
|
21054
21139
|
const createdArtifacts = [];
|
|
@@ -21151,9 +21236,9 @@ Ingest ended with error: ${message.subtype}`)
|
|
|
21151
21236
|
async function ingestCommand(file2, options) {
|
|
21152
21237
|
const project = loadProject();
|
|
21153
21238
|
const marvinDir = project.marvinDir;
|
|
21154
|
-
const sourcesDir =
|
|
21155
|
-
if (!
|
|
21156
|
-
|
|
21239
|
+
const sourcesDir = path12.join(marvinDir, "sources");
|
|
21240
|
+
if (!fs12.existsSync(sourcesDir)) {
|
|
21241
|
+
fs12.mkdirSync(sourcesDir, { recursive: true });
|
|
21157
21242
|
}
|
|
21158
21243
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21159
21244
|
manifest.scan();
|
|
@@ -21164,8 +21249,8 @@ async function ingestCommand(file2, options) {
|
|
|
21164
21249
|
return;
|
|
21165
21250
|
}
|
|
21166
21251
|
if (file2) {
|
|
21167
|
-
const filePath =
|
|
21168
|
-
if (!
|
|
21252
|
+
const filePath = path12.join(sourcesDir, file2);
|
|
21253
|
+
if (!fs12.existsSync(filePath)) {
|
|
21169
21254
|
console.log(chalk8.red(`Source file not found: ${file2}`));
|
|
21170
21255
|
console.log(chalk8.dim(`Expected at: ${filePath}`));
|
|
21171
21256
|
console.log(chalk8.dim(`Drop files into .marvin/sources/ and try again.`));
|
|
@@ -21232,7 +21317,7 @@ import ora3 from "ora";
|
|
|
21232
21317
|
import { input as input3 } from "@inquirer/prompts";
|
|
21233
21318
|
|
|
21234
21319
|
// src/git/repository.ts
|
|
21235
|
-
import * as
|
|
21320
|
+
import * as path13 from "path";
|
|
21236
21321
|
import simpleGit from "simple-git";
|
|
21237
21322
|
var MARVIN_GITIGNORE = `node_modules/
|
|
21238
21323
|
.DS_Store
|
|
@@ -21252,7 +21337,7 @@ var DIR_TYPE_LABELS = {
|
|
|
21252
21337
|
function buildCommitMessage(files) {
|
|
21253
21338
|
const counts = /* @__PURE__ */ new Map();
|
|
21254
21339
|
for (const f of files) {
|
|
21255
|
-
const parts2 = f.split(
|
|
21340
|
+
const parts2 = f.split(path13.sep).join("/").split("/");
|
|
21256
21341
|
const docsIdx = parts2.indexOf("docs");
|
|
21257
21342
|
if (docsIdx !== -1 && docsIdx + 1 < parts2.length) {
|
|
21258
21343
|
const dirName = parts2[docsIdx + 1];
|
|
@@ -21292,9 +21377,9 @@ var MarvinGit = class {
|
|
|
21292
21377
|
);
|
|
21293
21378
|
}
|
|
21294
21379
|
await this.git.init();
|
|
21295
|
-
const { writeFileSync:
|
|
21296
|
-
|
|
21297
|
-
|
|
21380
|
+
const { writeFileSync: writeFileSync10 } = await import("fs");
|
|
21381
|
+
writeFileSync10(
|
|
21382
|
+
path13.join(this.marvinDir, ".gitignore"),
|
|
21298
21383
|
MARVIN_GITIGNORE,
|
|
21299
21384
|
"utf-8"
|
|
21300
21385
|
);
|
|
@@ -21414,9 +21499,9 @@ var MarvinGit = class {
|
|
|
21414
21499
|
}
|
|
21415
21500
|
}
|
|
21416
21501
|
static async clone(url2, targetDir) {
|
|
21417
|
-
const marvinDir =
|
|
21418
|
-
const { existsSync:
|
|
21419
|
-
if (
|
|
21502
|
+
const marvinDir = path13.join(targetDir, ".marvin");
|
|
21503
|
+
const { existsSync: existsSync17 } = await import("fs");
|
|
21504
|
+
if (existsSync17(marvinDir)) {
|
|
21420
21505
|
throw new GitSyncError(
|
|
21421
21506
|
`.marvin/ already exists at ${targetDir}. Remove it first or choose a different directory.`
|
|
21422
21507
|
);
|
|
@@ -21594,8 +21679,8 @@ async function cloneCommand(url2, directory) {
|
|
|
21594
21679
|
}
|
|
21595
21680
|
|
|
21596
21681
|
// src/mcp/stdio-server.ts
|
|
21597
|
-
import * as
|
|
21598
|
-
import * as
|
|
21682
|
+
import * as fs13 from "fs";
|
|
21683
|
+
import * as path14 from "path";
|
|
21599
21684
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21600
21685
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
21601
21686
|
|
|
@@ -21893,8 +21978,8 @@ function collectTools(marvinDir) {
|
|
|
21893
21978
|
const plugin = resolvePlugin(config2.methodology);
|
|
21894
21979
|
const registrations = plugin?.documentTypeRegistrations ?? [];
|
|
21895
21980
|
const store = new DocumentStore(marvinDir, registrations);
|
|
21896
|
-
const sourcesDir =
|
|
21897
|
-
const hasSourcesDir =
|
|
21981
|
+
const sourcesDir = path14.join(marvinDir, "sources");
|
|
21982
|
+
const hasSourcesDir = fs13.existsSync(sourcesDir);
|
|
21898
21983
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
21899
21984
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
21900
21985
|
const sessionStore = new SessionStore(marvinDir);
|
|
@@ -21902,8 +21987,14 @@ function collectTools(marvinDir) {
|
|
|
21902
21987
|
const allSkillIds = [...allSkills.keys()];
|
|
21903
21988
|
const codeSkillTools = getSkillTools(allSkillIds, allSkills, store);
|
|
21904
21989
|
const skillsWithActions = allSkillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
|
|
21905
|
-
const projectRoot =
|
|
21990
|
+
const projectRoot = path14.dirname(marvinDir);
|
|
21906
21991
|
const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
|
|
21992
|
+
const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
|
|
21993
|
+
const navGroups = buildNavGroups({
|
|
21994
|
+
pluginRegs: registrations,
|
|
21995
|
+
skillRegs: allSkillRegs,
|
|
21996
|
+
pluginName: plugin?.name
|
|
21997
|
+
});
|
|
21907
21998
|
return [
|
|
21908
21999
|
...createDecisionTools(store),
|
|
21909
22000
|
...createActionTools(store),
|
|
@@ -21911,6 +22002,7 @@ function collectTools(marvinDir) {
|
|
|
21911
22002
|
...createDocumentTools(store),
|
|
21912
22003
|
...manifest ? createSourceTools(manifest) : [],
|
|
21913
22004
|
...createSessionTools(sessionStore),
|
|
22005
|
+
...createWebTools(store, config2.name, navGroups),
|
|
21914
22006
|
...pluginTools,
|
|
21915
22007
|
...codeSkillTools,
|
|
21916
22008
|
...actionTools
|
|
@@ -21965,8 +22057,8 @@ async function serveCommand() {
|
|
|
21965
22057
|
}
|
|
21966
22058
|
|
|
21967
22059
|
// src/cli/commands/skills.ts
|
|
21968
|
-
import * as
|
|
21969
|
-
import * as
|
|
22060
|
+
import * as fs14 from "fs";
|
|
22061
|
+
import * as path15 from "path";
|
|
21970
22062
|
import * as YAML7 from "yaml";
|
|
21971
22063
|
import matter3 from "gray-matter";
|
|
21972
22064
|
import chalk10 from "chalk";
|
|
@@ -22072,14 +22164,14 @@ async function skillsRemoveCommand(skillId, options) {
|
|
|
22072
22164
|
}
|
|
22073
22165
|
async function skillsCreateCommand(name) {
|
|
22074
22166
|
const project = loadProject();
|
|
22075
|
-
const skillsDir =
|
|
22076
|
-
|
|
22077
|
-
const skillDir =
|
|
22078
|
-
if (
|
|
22167
|
+
const skillsDir = path15.join(project.marvinDir, "skills");
|
|
22168
|
+
fs14.mkdirSync(skillsDir, { recursive: true });
|
|
22169
|
+
const skillDir = path15.join(skillsDir, name);
|
|
22170
|
+
if (fs14.existsSync(skillDir)) {
|
|
22079
22171
|
console.log(chalk10.yellow(`Skill directory already exists: ${skillDir}`));
|
|
22080
22172
|
return;
|
|
22081
22173
|
}
|
|
22082
|
-
|
|
22174
|
+
fs14.mkdirSync(skillDir, { recursive: true });
|
|
22083
22175
|
const displayName = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
22084
22176
|
const frontmatter = {
|
|
22085
22177
|
name,
|
|
@@ -22093,7 +22185,7 @@ async function skillsCreateCommand(name) {
|
|
|
22093
22185
|
You have the **${displayName}** skill.
|
|
22094
22186
|
`;
|
|
22095
22187
|
const skillMd = matter3.stringify(body, frontmatter);
|
|
22096
|
-
|
|
22188
|
+
fs14.writeFileSync(path15.join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
22097
22189
|
const actions = [
|
|
22098
22190
|
{
|
|
22099
22191
|
id: "run",
|
|
@@ -22103,7 +22195,7 @@ You have the **${displayName}** skill.
|
|
|
22103
22195
|
maxTurns: 5
|
|
22104
22196
|
}
|
|
22105
22197
|
];
|
|
22106
|
-
|
|
22198
|
+
fs14.writeFileSync(path15.join(skillDir, "actions.yaml"), YAML7.stringify(actions), "utf-8");
|
|
22107
22199
|
console.log(chalk10.green(`Created skill: ${skillDir}/`));
|
|
22108
22200
|
console.log(chalk10.dim(" SKILL.md \u2014 skill definition and prompt"));
|
|
22109
22201
|
console.log(chalk10.dim(" actions.yaml \u2014 action definitions"));
|
|
@@ -22111,14 +22203,14 @@ You have the **${displayName}** skill.
|
|
|
22111
22203
|
}
|
|
22112
22204
|
async function skillsMigrateCommand() {
|
|
22113
22205
|
const project = loadProject();
|
|
22114
|
-
const skillsDir =
|
|
22115
|
-
if (!
|
|
22206
|
+
const skillsDir = path15.join(project.marvinDir, "skills");
|
|
22207
|
+
if (!fs14.existsSync(skillsDir)) {
|
|
22116
22208
|
console.log(chalk10.dim("No skills directory found."));
|
|
22117
22209
|
return;
|
|
22118
22210
|
}
|
|
22119
22211
|
let entries;
|
|
22120
22212
|
try {
|
|
22121
|
-
entries =
|
|
22213
|
+
entries = fs14.readdirSync(skillsDir);
|
|
22122
22214
|
} catch {
|
|
22123
22215
|
console.log(chalk10.red("Could not read skills directory."));
|
|
22124
22216
|
return;
|
|
@@ -22130,16 +22222,16 @@ async function skillsMigrateCommand() {
|
|
|
22130
22222
|
}
|
|
22131
22223
|
let migrated = 0;
|
|
22132
22224
|
for (const file2 of yamlFiles) {
|
|
22133
|
-
const yamlPath =
|
|
22225
|
+
const yamlPath = path15.join(skillsDir, file2);
|
|
22134
22226
|
const baseName = file2.replace(/\.(yaml|yml)$/, "");
|
|
22135
|
-
const outputDir =
|
|
22136
|
-
if (
|
|
22227
|
+
const outputDir = path15.join(skillsDir, baseName);
|
|
22228
|
+
if (fs14.existsSync(outputDir)) {
|
|
22137
22229
|
console.log(chalk10.yellow(`Skipping "${file2}" \u2014 directory "${baseName}/" already exists.`));
|
|
22138
22230
|
continue;
|
|
22139
22231
|
}
|
|
22140
22232
|
try {
|
|
22141
22233
|
migrateYamlToSkillMd(yamlPath, outputDir);
|
|
22142
|
-
|
|
22234
|
+
fs14.renameSync(yamlPath, `${yamlPath}.bak`);
|
|
22143
22235
|
console.log(chalk10.green(`Migrated "${file2}" \u2192 "${baseName}/"`));
|
|
22144
22236
|
migrated++;
|
|
22145
22237
|
} catch (err) {
|
|
@@ -22153,35 +22245,35 @@ ${migrated} skill(s) migrated. Original files renamed to *.bak`));
|
|
|
22153
22245
|
}
|
|
22154
22246
|
|
|
22155
22247
|
// src/cli/commands/import.ts
|
|
22156
|
-
import * as
|
|
22157
|
-
import * as
|
|
22248
|
+
import * as fs17 from "fs";
|
|
22249
|
+
import * as path18 from "path";
|
|
22158
22250
|
import chalk11 from "chalk";
|
|
22159
22251
|
|
|
22160
22252
|
// src/import/engine.ts
|
|
22161
|
-
import * as
|
|
22162
|
-
import * as
|
|
22253
|
+
import * as fs16 from "fs";
|
|
22254
|
+
import * as path17 from "path";
|
|
22163
22255
|
import matter5 from "gray-matter";
|
|
22164
22256
|
|
|
22165
22257
|
// src/import/classifier.ts
|
|
22166
|
-
import * as
|
|
22167
|
-
import * as
|
|
22258
|
+
import * as fs15 from "fs";
|
|
22259
|
+
import * as path16 from "path";
|
|
22168
22260
|
import matter4 from "gray-matter";
|
|
22169
22261
|
var RAW_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".txt"]);
|
|
22170
22262
|
var CORE_DIR_NAMES = /* @__PURE__ */ new Set(["decisions", "actions", "questions"]);
|
|
22171
22263
|
var ID_PATTERN = /^[A-Z]+-\d{3,}$/;
|
|
22172
22264
|
function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
22173
|
-
const resolved =
|
|
22174
|
-
const stat =
|
|
22265
|
+
const resolved = path16.resolve(inputPath);
|
|
22266
|
+
const stat = fs15.statSync(resolved);
|
|
22175
22267
|
if (!stat.isDirectory()) {
|
|
22176
22268
|
return classifyFile(resolved, knownTypes);
|
|
22177
22269
|
}
|
|
22178
|
-
if (
|
|
22270
|
+
if (path16.basename(resolved) === ".marvin" || fs15.existsSync(path16.join(resolved, "config.yaml"))) {
|
|
22179
22271
|
return { type: "marvin-project", inputPath: resolved };
|
|
22180
22272
|
}
|
|
22181
22273
|
const allDirNames = /* @__PURE__ */ new Set([...CORE_DIR_NAMES, ...knownDirNames]);
|
|
22182
|
-
const entries =
|
|
22274
|
+
const entries = fs15.readdirSync(resolved);
|
|
22183
22275
|
const hasDocSubdirs = entries.some(
|
|
22184
|
-
(e) => allDirNames.has(e) &&
|
|
22276
|
+
(e) => allDirNames.has(e) && fs15.statSync(path16.join(resolved, e)).isDirectory()
|
|
22185
22277
|
);
|
|
22186
22278
|
if (hasDocSubdirs) {
|
|
22187
22279
|
return { type: "docs-directory", inputPath: resolved };
|
|
@@ -22190,7 +22282,7 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22190
22282
|
if (mdFiles.length > 0) {
|
|
22191
22283
|
const hasMarvinDocs = mdFiles.some((f) => {
|
|
22192
22284
|
try {
|
|
22193
|
-
const raw =
|
|
22285
|
+
const raw = fs15.readFileSync(path16.join(resolved, f), "utf-8");
|
|
22194
22286
|
const { data } = matter4(raw);
|
|
22195
22287
|
return isValidMarvinDocument(data, knownTypes);
|
|
22196
22288
|
} catch {
|
|
@@ -22204,14 +22296,14 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22204
22296
|
return { type: "raw-source-dir", inputPath: resolved };
|
|
22205
22297
|
}
|
|
22206
22298
|
function classifyFile(filePath, knownTypes) {
|
|
22207
|
-
const resolved =
|
|
22208
|
-
const ext =
|
|
22299
|
+
const resolved = path16.resolve(filePath);
|
|
22300
|
+
const ext = path16.extname(resolved).toLowerCase();
|
|
22209
22301
|
if (RAW_SOURCE_EXTENSIONS.has(ext)) {
|
|
22210
22302
|
return { type: "raw-source-file", inputPath: resolved };
|
|
22211
22303
|
}
|
|
22212
22304
|
if (ext === ".md") {
|
|
22213
22305
|
try {
|
|
22214
|
-
const raw =
|
|
22306
|
+
const raw = fs15.readFileSync(resolved, "utf-8");
|
|
22215
22307
|
const { data } = matter4(raw);
|
|
22216
22308
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22217
22309
|
return { type: "marvin-document", inputPath: resolved };
|
|
@@ -22336,9 +22428,9 @@ function executeImportPlan(plan, store, marvinDir, options) {
|
|
|
22336
22428
|
continue;
|
|
22337
22429
|
}
|
|
22338
22430
|
if (item.action === "copy") {
|
|
22339
|
-
const targetDir =
|
|
22340
|
-
|
|
22341
|
-
|
|
22431
|
+
const targetDir = path17.dirname(item.targetPath);
|
|
22432
|
+
fs16.mkdirSync(targetDir, { recursive: true });
|
|
22433
|
+
fs16.copyFileSync(item.sourcePath, item.targetPath);
|
|
22342
22434
|
copied++;
|
|
22343
22435
|
continue;
|
|
22344
22436
|
}
|
|
@@ -22374,19 +22466,19 @@ function formatPlanSummary(plan) {
|
|
|
22374
22466
|
lines.push(`Documents to import: ${imports.length}`);
|
|
22375
22467
|
for (const item of imports) {
|
|
22376
22468
|
const idInfo = item.originalId !== item.newId ? `${item.originalId} \u2192 ${item.newId}` : item.newId ?? item.originalId ?? "";
|
|
22377
|
-
lines.push(` ${idInfo} ${
|
|
22469
|
+
lines.push(` ${idInfo} ${path17.basename(item.sourcePath)}`);
|
|
22378
22470
|
}
|
|
22379
22471
|
}
|
|
22380
22472
|
if (copies.length > 0) {
|
|
22381
22473
|
lines.push(`Files to copy to sources/: ${copies.length}`);
|
|
22382
22474
|
for (const item of copies) {
|
|
22383
|
-
lines.push(` ${
|
|
22475
|
+
lines.push(` ${path17.basename(item.sourcePath)} \u2192 ${path17.basename(item.targetPath)}`);
|
|
22384
22476
|
}
|
|
22385
22477
|
}
|
|
22386
22478
|
if (skips.length > 0) {
|
|
22387
22479
|
lines.push(`Skipped (conflict): ${skips.length}`);
|
|
22388
22480
|
for (const item of skips) {
|
|
22389
|
-
lines.push(` ${item.originalId ??
|
|
22481
|
+
lines.push(` ${item.originalId ?? path17.basename(item.sourcePath)} ${item.reason ?? ""}`);
|
|
22390
22482
|
}
|
|
22391
22483
|
}
|
|
22392
22484
|
if (plan.items.length === 0) {
|
|
@@ -22419,11 +22511,11 @@ function getDirNameForType(store, type) {
|
|
|
22419
22511
|
}
|
|
22420
22512
|
function collectMarvinDocs(dir, knownTypes) {
|
|
22421
22513
|
const docs = [];
|
|
22422
|
-
const files =
|
|
22514
|
+
const files = fs16.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
22423
22515
|
for (const file2 of files) {
|
|
22424
|
-
const filePath =
|
|
22516
|
+
const filePath = path17.join(dir, file2);
|
|
22425
22517
|
try {
|
|
22426
|
-
const raw =
|
|
22518
|
+
const raw = fs16.readFileSync(filePath, "utf-8");
|
|
22427
22519
|
const { data, content } = matter5(raw);
|
|
22428
22520
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22429
22521
|
docs.push({
|
|
@@ -22479,23 +22571,23 @@ function planDocImports(docs, store, options) {
|
|
|
22479
22571
|
}
|
|
22480
22572
|
function planFromMarvinProject(classification, store, _marvinDir, options) {
|
|
22481
22573
|
let projectDir = classification.inputPath;
|
|
22482
|
-
if (
|
|
22483
|
-
const inner =
|
|
22484
|
-
if (
|
|
22574
|
+
if (path17.basename(projectDir) !== ".marvin") {
|
|
22575
|
+
const inner = path17.join(projectDir, ".marvin");
|
|
22576
|
+
if (fs16.existsSync(inner)) {
|
|
22485
22577
|
projectDir = inner;
|
|
22486
22578
|
}
|
|
22487
22579
|
}
|
|
22488
|
-
const docsDir =
|
|
22489
|
-
if (!
|
|
22580
|
+
const docsDir = path17.join(projectDir, "docs");
|
|
22581
|
+
if (!fs16.existsSync(docsDir)) {
|
|
22490
22582
|
return [];
|
|
22491
22583
|
}
|
|
22492
22584
|
const knownTypes = store.registeredTypes;
|
|
22493
22585
|
const allDocs = [];
|
|
22494
|
-
const subdirs =
|
|
22495
|
-
(d) =>
|
|
22586
|
+
const subdirs = fs16.readdirSync(docsDir).filter(
|
|
22587
|
+
(d) => fs16.statSync(path17.join(docsDir, d)).isDirectory()
|
|
22496
22588
|
);
|
|
22497
22589
|
for (const subdir of subdirs) {
|
|
22498
|
-
const docs = collectMarvinDocs(
|
|
22590
|
+
const docs = collectMarvinDocs(path17.join(docsDir, subdir), knownTypes);
|
|
22499
22591
|
allDocs.push(...docs);
|
|
22500
22592
|
}
|
|
22501
22593
|
return planDocImports(allDocs, store, options);
|
|
@@ -22505,10 +22597,10 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22505
22597
|
const knownTypes = store.registeredTypes;
|
|
22506
22598
|
const allDocs = [];
|
|
22507
22599
|
allDocs.push(...collectMarvinDocs(dir, knownTypes));
|
|
22508
|
-
const entries =
|
|
22600
|
+
const entries = fs16.readdirSync(dir);
|
|
22509
22601
|
for (const entry of entries) {
|
|
22510
|
-
const entryPath =
|
|
22511
|
-
if (
|
|
22602
|
+
const entryPath = path17.join(dir, entry);
|
|
22603
|
+
if (fs16.statSync(entryPath).isDirectory()) {
|
|
22512
22604
|
allDocs.push(...collectMarvinDocs(entryPath, knownTypes));
|
|
22513
22605
|
}
|
|
22514
22606
|
}
|
|
@@ -22517,7 +22609,7 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22517
22609
|
function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
22518
22610
|
const filePath = classification.inputPath;
|
|
22519
22611
|
const knownTypes = store.registeredTypes;
|
|
22520
|
-
const raw =
|
|
22612
|
+
const raw = fs16.readFileSync(filePath, "utf-8");
|
|
22521
22613
|
const { data, content } = matter5(raw);
|
|
22522
22614
|
if (!isValidMarvinDocument(data, knownTypes)) {
|
|
22523
22615
|
return [];
|
|
@@ -22533,14 +22625,14 @@ function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
|
22533
22625
|
}
|
|
22534
22626
|
function planFromRawSourceDir(classification, marvinDir) {
|
|
22535
22627
|
const dir = classification.inputPath;
|
|
22536
|
-
const sourcesDir =
|
|
22628
|
+
const sourcesDir = path17.join(marvinDir, "sources");
|
|
22537
22629
|
const items = [];
|
|
22538
|
-
const files =
|
|
22539
|
-
const stat =
|
|
22630
|
+
const files = fs16.readdirSync(dir).filter((f) => {
|
|
22631
|
+
const stat = fs16.statSync(path17.join(dir, f));
|
|
22540
22632
|
return stat.isFile();
|
|
22541
22633
|
});
|
|
22542
22634
|
for (const file2 of files) {
|
|
22543
|
-
const sourcePath =
|
|
22635
|
+
const sourcePath = path17.join(dir, file2);
|
|
22544
22636
|
const targetPath = resolveSourceFileName(sourcesDir, file2);
|
|
22545
22637
|
items.push({
|
|
22546
22638
|
action: "copy",
|
|
@@ -22551,8 +22643,8 @@ function planFromRawSourceDir(classification, marvinDir) {
|
|
|
22551
22643
|
return items;
|
|
22552
22644
|
}
|
|
22553
22645
|
function planFromRawSourceFile(classification, marvinDir) {
|
|
22554
|
-
const sourcesDir =
|
|
22555
|
-
const fileName =
|
|
22646
|
+
const sourcesDir = path17.join(marvinDir, "sources");
|
|
22647
|
+
const fileName = path17.basename(classification.inputPath);
|
|
22556
22648
|
const targetPath = resolveSourceFileName(sourcesDir, fileName);
|
|
22557
22649
|
return [
|
|
22558
22650
|
{
|
|
@@ -22563,25 +22655,25 @@ function planFromRawSourceFile(classification, marvinDir) {
|
|
|
22563
22655
|
];
|
|
22564
22656
|
}
|
|
22565
22657
|
function resolveSourceFileName(sourcesDir, fileName) {
|
|
22566
|
-
const targetPath =
|
|
22567
|
-
if (!
|
|
22658
|
+
const targetPath = path17.join(sourcesDir, fileName);
|
|
22659
|
+
if (!fs16.existsSync(targetPath)) {
|
|
22568
22660
|
return targetPath;
|
|
22569
22661
|
}
|
|
22570
|
-
const ext =
|
|
22571
|
-
const base =
|
|
22662
|
+
const ext = path17.extname(fileName);
|
|
22663
|
+
const base = path17.basename(fileName, ext);
|
|
22572
22664
|
let counter = 1;
|
|
22573
22665
|
let candidate;
|
|
22574
22666
|
do {
|
|
22575
|
-
candidate =
|
|
22667
|
+
candidate = path17.join(sourcesDir, `${base}-${counter}${ext}`);
|
|
22576
22668
|
counter++;
|
|
22577
|
-
} while (
|
|
22669
|
+
} while (fs16.existsSync(candidate));
|
|
22578
22670
|
return candidate;
|
|
22579
22671
|
}
|
|
22580
22672
|
|
|
22581
22673
|
// src/cli/commands/import.ts
|
|
22582
22674
|
async function importCommand(inputPath, options) {
|
|
22583
|
-
const resolved =
|
|
22584
|
-
if (!
|
|
22675
|
+
const resolved = path18.resolve(inputPath);
|
|
22676
|
+
if (!fs17.existsSync(resolved)) {
|
|
22585
22677
|
throw new ImportError(`Path not found: ${resolved}`);
|
|
22586
22678
|
}
|
|
22587
22679
|
const project = loadProject();
|
|
@@ -22633,7 +22725,7 @@ async function importCommand(inputPath, options) {
|
|
|
22633
22725
|
console.log(chalk11.bold("\nStarting ingest of copied sources...\n"));
|
|
22634
22726
|
const manifest = new SourceManifestManager(marvinDir);
|
|
22635
22727
|
manifest.scan();
|
|
22636
|
-
const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) =>
|
|
22728
|
+
const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) => path18.basename(i.targetPath));
|
|
22637
22729
|
for (const fileName of copiedFileNames) {
|
|
22638
22730
|
try {
|
|
22639
22731
|
await ingestFile({
|
|
@@ -23036,7 +23128,8 @@ The contributor is identifying a project risk.
|
|
|
23036
23128
|
- Create actions for risk mitigation tasks
|
|
23037
23129
|
- Create decisions for risk response strategies
|
|
23038
23130
|
- Create questions for risks needing further assessment
|
|
23039
|
-
- Tag all related artifacts with "risk" for tracking
|
|
23131
|
+
- Tag all related artifacts with "risk" for tracking
|
|
23132
|
+
- 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`,
|
|
23040
23133
|
"blocker-report": `
|
|
23041
23134
|
### Type-Specific Guidance: Blocker Report
|
|
23042
23135
|
The contributor is reporting a blocker.
|
|
@@ -23520,12 +23613,38 @@ async function webCommand(options) {
|
|
|
23520
23613
|
await startWebServer({ port, open: options.open });
|
|
23521
23614
|
}
|
|
23522
23615
|
|
|
23616
|
+
// src/cli/commands/generate.ts
|
|
23617
|
+
import * as fs18 from "fs";
|
|
23618
|
+
import * as path19 from "path";
|
|
23619
|
+
import chalk18 from "chalk";
|
|
23620
|
+
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
23621
|
+
async function generateClaudeMdCommand(options) {
|
|
23622
|
+
const project = loadProject();
|
|
23623
|
+
const filePath = path19.join(project.marvinDir, "CLAUDE.md");
|
|
23624
|
+
if (fs18.existsSync(filePath) && !options.force) {
|
|
23625
|
+
const overwrite = await confirm2({
|
|
23626
|
+
message: ".marvin/CLAUDE.md already exists. Overwrite?",
|
|
23627
|
+
default: false
|
|
23628
|
+
});
|
|
23629
|
+
if (!overwrite) {
|
|
23630
|
+
console.log(chalk18.dim("Aborted."));
|
|
23631
|
+
return;
|
|
23632
|
+
}
|
|
23633
|
+
}
|
|
23634
|
+
fs18.writeFileSync(
|
|
23635
|
+
filePath,
|
|
23636
|
+
getDefaultClaudeMdContent(project.config.name),
|
|
23637
|
+
"utf-8"
|
|
23638
|
+
);
|
|
23639
|
+
console.log(chalk18.green("Created .marvin/CLAUDE.md"));
|
|
23640
|
+
}
|
|
23641
|
+
|
|
23523
23642
|
// src/cli/program.ts
|
|
23524
23643
|
function createProgram() {
|
|
23525
23644
|
const program2 = new Command();
|
|
23526
23645
|
program2.name("marvin").description(
|
|
23527
23646
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
23528
|
-
).version("0.3.
|
|
23647
|
+
).version("0.3.7");
|
|
23529
23648
|
program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
23530
23649
|
await initCommand();
|
|
23531
23650
|
});
|
|
@@ -23611,6 +23730,10 @@ function createProgram() {
|
|
|
23611
23730
|
program2.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) => {
|
|
23612
23731
|
await webCommand(options);
|
|
23613
23732
|
});
|
|
23733
|
+
const generateCmd = program2.command("generate").description("Generate project files");
|
|
23734
|
+
generateCmd.command("claude-md").description("Generate .marvin/CLAUDE.md project instruction file").option("--force", "Overwrite existing file without prompting").action(async (options) => {
|
|
23735
|
+
await generateClaudeMdCommand(options);
|
|
23736
|
+
});
|
|
23614
23737
|
return program2;
|
|
23615
23738
|
}
|
|
23616
23739
|
|