mrvn-cli 0.4.2 → 0.4.4
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/README.md +67 -23
- package/dist/index.js +1412 -320
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +1305 -213
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +1406 -314
- 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, path21) {
|
|
924
|
+
if (!path21)
|
|
925
925
|
return obj;
|
|
926
|
-
return
|
|
926
|
+
return path21.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(path21, 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(path21);
|
|
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, path21 = []) => {
|
|
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 = [...path21, ...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 path21 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1539
|
+
for (const seg of path21) {
|
|
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 path21 = ref.slice(1).split("/").filter(Boolean);
|
|
13517
|
+
if (path21.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 (path21[0] === defsKey) {
|
|
13522
|
+
const key = path21[1];
|
|
13523
13523
|
if (!key || !ctx.defs[key]) {
|
|
13524
13524
|
throw new Error(`Reference not found: ${ref}`);
|
|
13525
13525
|
}
|
|
@@ -15627,6 +15627,205 @@ function createSprintPlanningTools(store) {
|
|
|
15627
15627
|
];
|
|
15628
15628
|
}
|
|
15629
15629
|
|
|
15630
|
+
// src/plugins/builtin/tools/tasks.ts
|
|
15631
|
+
import { tool as tool8 } from "@anthropic-ai/claude-agent-sdk";
|
|
15632
|
+
|
|
15633
|
+
// src/plugins/builtin/tools/task-utils.ts
|
|
15634
|
+
function normalizeLinkedEpics(value) {
|
|
15635
|
+
if (value === void 0 || value === null) return [];
|
|
15636
|
+
if (typeof value === "string") {
|
|
15637
|
+
try {
|
|
15638
|
+
const parsed = JSON.parse(value);
|
|
15639
|
+
if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
|
|
15640
|
+
} catch {
|
|
15641
|
+
}
|
|
15642
|
+
return [value];
|
|
15643
|
+
}
|
|
15644
|
+
if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
|
|
15645
|
+
return [];
|
|
15646
|
+
}
|
|
15647
|
+
function generateEpicTags(epics) {
|
|
15648
|
+
return epics.map((id) => `epic:${id}`);
|
|
15649
|
+
}
|
|
15650
|
+
|
|
15651
|
+
// src/plugins/builtin/tools/tasks.ts
|
|
15652
|
+
var linkedEpicArray = external_exports.preprocess(
|
|
15653
|
+
(val) => {
|
|
15654
|
+
if (typeof val === "string") {
|
|
15655
|
+
try {
|
|
15656
|
+
const parsed = JSON.parse(val);
|
|
15657
|
+
if (Array.isArray(parsed)) return parsed;
|
|
15658
|
+
} catch {
|
|
15659
|
+
}
|
|
15660
|
+
return [val];
|
|
15661
|
+
}
|
|
15662
|
+
return val;
|
|
15663
|
+
},
|
|
15664
|
+
external_exports.array(external_exports.string())
|
|
15665
|
+
);
|
|
15666
|
+
function createTaskTools(store) {
|
|
15667
|
+
return [
|
|
15668
|
+
tool8(
|
|
15669
|
+
"list_tasks",
|
|
15670
|
+
"List all tasks in the project, optionally filtered by status, linked epic, or priority",
|
|
15671
|
+
{
|
|
15672
|
+
status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("Filter by task status"),
|
|
15673
|
+
linkedEpic: external_exports.string().optional().describe("Filter by linked epic ID (e.g. 'E-001')"),
|
|
15674
|
+
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Filter by priority")
|
|
15675
|
+
},
|
|
15676
|
+
async (args) => {
|
|
15677
|
+
let docs = store.list({ type: "task", status: args.status });
|
|
15678
|
+
if (args.linkedEpic) {
|
|
15679
|
+
docs = docs.filter(
|
|
15680
|
+
(d) => normalizeLinkedEpics(d.frontmatter.linkedEpic).includes(args.linkedEpic)
|
|
15681
|
+
);
|
|
15682
|
+
}
|
|
15683
|
+
if (args.priority) {
|
|
15684
|
+
docs = docs.filter((d) => d.frontmatter.priority === args.priority);
|
|
15685
|
+
}
|
|
15686
|
+
const summary = docs.map((d) => ({
|
|
15687
|
+
id: d.frontmatter.id,
|
|
15688
|
+
title: d.frontmatter.title,
|
|
15689
|
+
status: d.frontmatter.status,
|
|
15690
|
+
linkedEpic: normalizeLinkedEpics(d.frontmatter.linkedEpic),
|
|
15691
|
+
priority: d.frontmatter.priority,
|
|
15692
|
+
complexity: d.frontmatter.complexity,
|
|
15693
|
+
estimatedPoints: d.frontmatter.estimatedPoints,
|
|
15694
|
+
tags: d.frontmatter.tags
|
|
15695
|
+
}));
|
|
15696
|
+
return {
|
|
15697
|
+
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
15698
|
+
};
|
|
15699
|
+
},
|
|
15700
|
+
{ annotations: { readOnlyHint: true } }
|
|
15701
|
+
),
|
|
15702
|
+
tool8(
|
|
15703
|
+
"get_task",
|
|
15704
|
+
"Get the full content of a specific task by ID",
|
|
15705
|
+
{ id: external_exports.string().describe("Task ID (e.g. 'T-001')") },
|
|
15706
|
+
async (args) => {
|
|
15707
|
+
const doc = store.get(args.id);
|
|
15708
|
+
if (!doc) {
|
|
15709
|
+
return {
|
|
15710
|
+
content: [{ type: "text", text: `Task ${args.id} not found` }],
|
|
15711
|
+
isError: true
|
|
15712
|
+
};
|
|
15713
|
+
}
|
|
15714
|
+
return {
|
|
15715
|
+
content: [
|
|
15716
|
+
{
|
|
15717
|
+
type: "text",
|
|
15718
|
+
text: JSON.stringify(
|
|
15719
|
+
{ ...doc.frontmatter, content: doc.content },
|
|
15720
|
+
null,
|
|
15721
|
+
2
|
|
15722
|
+
)
|
|
15723
|
+
}
|
|
15724
|
+
]
|
|
15725
|
+
};
|
|
15726
|
+
},
|
|
15727
|
+
{ annotations: { readOnlyHint: true } }
|
|
15728
|
+
),
|
|
15729
|
+
tool8(
|
|
15730
|
+
"create_task",
|
|
15731
|
+
"Create a new implementation task linked to one or more epics. The linked epic is soft-validated (warns if not found, but does not block creation).",
|
|
15732
|
+
{
|
|
15733
|
+
title: external_exports.string().describe("Task title"),
|
|
15734
|
+
content: external_exports.string().describe("Task description and implementation details"),
|
|
15735
|
+
linkedEpic: linkedEpicArray.describe("Epic ID(s) to link this task to (e.g. ['E-001'] or ['E-001', 'E-002'])"),
|
|
15736
|
+
status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("Task status (default: 'backlog')"),
|
|
15737
|
+
acceptanceCriteria: external_exports.string().optional().describe("Acceptance criteria for the task"),
|
|
15738
|
+
technicalNotes: external_exports.string().optional().describe("Technical implementation notes"),
|
|
15739
|
+
estimatedPoints: external_exports.number().optional().describe("Story point estimate"),
|
|
15740
|
+
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("Task complexity"),
|
|
15741
|
+
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Task priority"),
|
|
15742
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
|
|
15743
|
+
},
|
|
15744
|
+
async (args) => {
|
|
15745
|
+
const linkedEpics = normalizeLinkedEpics(args.linkedEpic);
|
|
15746
|
+
const warnings = [];
|
|
15747
|
+
for (const epicId of linkedEpics) {
|
|
15748
|
+
const epic = store.get(epicId);
|
|
15749
|
+
if (!epic) {
|
|
15750
|
+
warnings.push(`Warning: Epic ${epicId} not found`);
|
|
15751
|
+
} else if (epic.frontmatter.type !== "epic") {
|
|
15752
|
+
warnings.push(`Warning: ${epicId} is a ${epic.frontmatter.type}, not an epic`);
|
|
15753
|
+
}
|
|
15754
|
+
}
|
|
15755
|
+
const frontmatter = {
|
|
15756
|
+
title: args.title,
|
|
15757
|
+
status: args.status ?? "backlog",
|
|
15758
|
+
linkedEpic: linkedEpics,
|
|
15759
|
+
tags: [...generateEpicTags(linkedEpics), ...args.tags ?? []]
|
|
15760
|
+
};
|
|
15761
|
+
if (args.acceptanceCriteria) frontmatter.acceptanceCriteria = args.acceptanceCriteria;
|
|
15762
|
+
if (args.technicalNotes) frontmatter.technicalNotes = args.technicalNotes;
|
|
15763
|
+
if (args.estimatedPoints !== void 0) frontmatter.estimatedPoints = args.estimatedPoints;
|
|
15764
|
+
if (args.complexity) frontmatter.complexity = args.complexity;
|
|
15765
|
+
if (args.priority) frontmatter.priority = args.priority;
|
|
15766
|
+
const doc = store.create("task", frontmatter, args.content);
|
|
15767
|
+
const parts = [
|
|
15768
|
+
`Created task ${doc.frontmatter.id}: ${doc.frontmatter.title} (linked to ${linkedEpics.join(", ")})`
|
|
15769
|
+
];
|
|
15770
|
+
if (warnings.length > 0) {
|
|
15771
|
+
parts.push(warnings.join("; "));
|
|
15772
|
+
}
|
|
15773
|
+
return {
|
|
15774
|
+
content: [{ type: "text", text: parts.join("\n") }]
|
|
15775
|
+
};
|
|
15776
|
+
}
|
|
15777
|
+
),
|
|
15778
|
+
tool8(
|
|
15779
|
+
"update_task",
|
|
15780
|
+
"Update an existing task, including its linked epics.",
|
|
15781
|
+
{
|
|
15782
|
+
id: external_exports.string().describe("Task ID to update"),
|
|
15783
|
+
title: external_exports.string().optional().describe("New title"),
|
|
15784
|
+
status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("New status"),
|
|
15785
|
+
content: external_exports.string().optional().describe("New content"),
|
|
15786
|
+
linkedEpic: linkedEpicArray.optional().describe("New linked epic ID(s)"),
|
|
15787
|
+
acceptanceCriteria: external_exports.string().optional().describe("New acceptance criteria"),
|
|
15788
|
+
technicalNotes: external_exports.string().optional().describe("New technical notes"),
|
|
15789
|
+
estimatedPoints: external_exports.number().optional().describe("New story point estimate"),
|
|
15790
|
+
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("New complexity"),
|
|
15791
|
+
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
|
|
15792
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)")
|
|
15793
|
+
},
|
|
15794
|
+
async (args) => {
|
|
15795
|
+
const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, ...updates } = args;
|
|
15796
|
+
const warnings = [];
|
|
15797
|
+
if (rawLinkedEpic !== void 0) {
|
|
15798
|
+
const linkedEpics = normalizeLinkedEpics(rawLinkedEpic);
|
|
15799
|
+
for (const epicId of linkedEpics) {
|
|
15800
|
+
const epic = store.get(epicId);
|
|
15801
|
+
if (!epic) {
|
|
15802
|
+
warnings.push(`Warning: Epic ${epicId} not found`);
|
|
15803
|
+
} else if (epic.frontmatter.type !== "epic") {
|
|
15804
|
+
warnings.push(`Warning: ${epicId} is a ${epic.frontmatter.type}, not an epic`);
|
|
15805
|
+
}
|
|
15806
|
+
}
|
|
15807
|
+
updates.linkedEpic = linkedEpics;
|
|
15808
|
+
const existingDoc = store.get(id);
|
|
15809
|
+
const existingTags = existingDoc?.frontmatter.tags ?? [];
|
|
15810
|
+
const nonEpicTags = existingTags.filter((t) => !t.startsWith("epic:"));
|
|
15811
|
+
const baseTags = userTags ?? nonEpicTags;
|
|
15812
|
+
updates.tags = [...generateEpicTags(linkedEpics), ...baseTags];
|
|
15813
|
+
} else if (userTags !== void 0) {
|
|
15814
|
+
updates.tags = userTags;
|
|
15815
|
+
}
|
|
15816
|
+
const doc = store.update(id, updates, content);
|
|
15817
|
+
const parts = [`Updated task ${doc.frontmatter.id}: ${doc.frontmatter.title}`];
|
|
15818
|
+
if (warnings.length > 0) {
|
|
15819
|
+
parts.push(warnings.join("; "));
|
|
15820
|
+
}
|
|
15821
|
+
return {
|
|
15822
|
+
content: [{ type: "text", text: parts.join("\n") }]
|
|
15823
|
+
};
|
|
15824
|
+
}
|
|
15825
|
+
)
|
|
15826
|
+
];
|
|
15827
|
+
}
|
|
15828
|
+
|
|
15630
15829
|
// src/plugins/common.ts
|
|
15631
15830
|
var COMMON_REGISTRATIONS = [
|
|
15632
15831
|
{ type: "meeting", dirName: "meetings", idPrefix: "M" },
|
|
@@ -15634,7 +15833,8 @@ var COMMON_REGISTRATIONS = [
|
|
|
15634
15833
|
{ type: "feature", dirName: "features", idPrefix: "F" },
|
|
15635
15834
|
{ type: "epic", dirName: "epics", idPrefix: "E" },
|
|
15636
15835
|
{ type: "contribution", dirName: "contributions", idPrefix: "C" },
|
|
15637
|
-
{ type: "sprint", dirName: "sprints", idPrefix: "SP" }
|
|
15836
|
+
{ type: "sprint", dirName: "sprints", idPrefix: "SP" },
|
|
15837
|
+
{ type: "task", dirName: "tasks", idPrefix: "T" }
|
|
15638
15838
|
];
|
|
15639
15839
|
function createCommonTools(store) {
|
|
15640
15840
|
return [
|
|
@@ -15644,7 +15844,8 @@ function createCommonTools(store) {
|
|
|
15644
15844
|
...createEpicTools(store),
|
|
15645
15845
|
...createContributionTools(store),
|
|
15646
15846
|
...createSprintTools(store),
|
|
15647
|
-
...createSprintPlanningTools(store)
|
|
15847
|
+
...createSprintPlanningTools(store),
|
|
15848
|
+
...createTaskTools(store)
|
|
15648
15849
|
];
|
|
15649
15850
|
}
|
|
15650
15851
|
|
|
@@ -15654,7 +15855,7 @@ var genericAgilePlugin = {
|
|
|
15654
15855
|
name: "Generic Agile",
|
|
15655
15856
|
description: "Default methodology plugin providing standard agile governance patterns for decisions, actions, and questions.",
|
|
15656
15857
|
version: "0.1.0",
|
|
15657
|
-
documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic", "contribution", "sprint"],
|
|
15858
|
+
documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic", "contribution", "sprint", "task"],
|
|
15658
15859
|
documentTypeRegistrations: [...COMMON_REGISTRATIONS],
|
|
15659
15860
|
tools: (store) => [...createCommonTools(store)],
|
|
15660
15861
|
promptFragments: {
|
|
@@ -15693,6 +15894,11 @@ var genericAgilePlugin = {
|
|
|
15693
15894
|
- **create_epic**: Create implementation epics linked to approved features. The system enforces that the linked feature must exist and be approved \u2014 if it's still "draft", ask the Product Owner to approve it first.
|
|
15694
15895
|
- **update_epic**: Update epic status (planned \u2192 in-progress \u2192 done), owner, and other fields.
|
|
15695
15896
|
|
|
15897
|
+
**Task Tools:**
|
|
15898
|
+
- **list_tasks** / **get_task**: Browse and read implementation tasks.
|
|
15899
|
+
- **create_task**: Create implementation tasks linked to epics. Linked epics are soft-validated (warns if not found, does not block). Tasks auto-generate \`epic:E-xxx\` tags. Default status: "backlog".
|
|
15900
|
+
- **update_task**: Update task status (backlog \u2192 ready \u2192 in-progress \u2192 review \u2192 done), acceptance criteria, technical notes, complexity, priority, and estimated points.
|
|
15901
|
+
|
|
15696
15902
|
**Feature Tools (read-only for awareness):**
|
|
15697
15903
|
- **list_features** / **get_feature**: View features to understand what needs to be broken into epics.
|
|
15698
15904
|
|
|
@@ -15704,6 +15910,7 @@ var genericAgilePlugin = {
|
|
|
15704
15910
|
|
|
15705
15911
|
**Key Workflow Rules:**
|
|
15706
15912
|
- Only create epics against approved features \u2014 create_epic enforces this.
|
|
15913
|
+
- Break epics into tasks (T-xxx) with clear acceptance criteria and complexity estimates.
|
|
15707
15914
|
- Tag work items (actions, decisions, questions) with \`epic:E-xxx\` to group them under an epic.
|
|
15708
15915
|
- Collaborate with the Delivery Manager on target dates and effort estimates.
|
|
15709
15916
|
- Each epic should have a clear scope and definition of done.
|
|
@@ -15739,6 +15946,9 @@ var genericAgilePlugin = {
|
|
|
15739
15946
|
- **list_epics** / **get_epic**: View epics and their current status.
|
|
15740
15947
|
- **update_epic**: Set targetDate and estimatedEffort on epics. Flag epics linked to deferred features.
|
|
15741
15948
|
|
|
15949
|
+
**Task Tools (read-only for tracking):**
|
|
15950
|
+
- **list_tasks** / **get_task**: View tasks and their statuses. Filter by linkedEpic to see implementation breakdown.
|
|
15951
|
+
|
|
15742
15952
|
**Feature Tools (tracking focus):**
|
|
15743
15953
|
- **list_features** / **get_feature**: View features and their priorities.
|
|
15744
15954
|
|
|
@@ -15784,14 +15994,15 @@ var genericAgilePlugin = {
|
|
|
15784
15994
|
- Reason through: priority (critical/high features first), capacity (compare backlog effort to velocity reference), dependencies and blockers, balance across features, and risk.
|
|
15785
15995
|
- Present a structured sprint proposal: title, goal, suggested dates, selected epics with rationale for each, excluded epics with reason, and identified risks.
|
|
15786
15996
|
- After user confirmation, use **create_sprint** with the agreed epics to persist the sprint.`,
|
|
15787
|
-
"*": `You have access to feature, epic, sprint, and meeting tools for project coordination:
|
|
15997
|
+
"*": `You have access to feature, epic, task, sprint, and meeting tools for project coordination:
|
|
15788
15998
|
|
|
15789
15999
|
**Features** (F-xxx): Product capabilities defined by the Product Owner. Features progress through draft \u2192 approved \u2192 done.
|
|
15790
16000
|
**Epics** (E-xxx): Implementation work packages created by the Tech Lead, linked to approved features. Epics progress through planned \u2192 in-progress \u2192 done.
|
|
16001
|
+
**Tasks** (T-xxx): Concrete implementation items created by the Tech Lead, linked to epics. Tasks progress through backlog \u2192 ready \u2192 in-progress \u2192 review \u2192 done.
|
|
15791
16002
|
**Sprints** (SP-xxx): Time-boxed iterations that group epics and work items with delivery dates. Sprints progress through planned \u2192 active \u2192 completed (or cancelled).
|
|
15792
16003
|
**Meetings**: Meeting records with attendees, agendas, and notes.
|
|
15793
16004
|
|
|
15794
|
-
**Key workflow rule:** Epics must link to approved features \u2014 the system enforces this. The Product Owner defines and approves features, the Tech Lead breaks them into epics, the Delivery Manager plans sprints and tracks dates and progress. Work items are associated with sprints via \`sprint:SP-xxx\` tags. Actions support a \`dueDate\` field for schedule tracking \u2014 actions with a past due date are automatically flagged as overdue in GAR reports. Use the \`sprints\` parameter on create_action/update_action to assign actions to sprints.
|
|
16005
|
+
**Key workflow rule:** Epics must link to approved features \u2014 the system enforces this. The Product Owner defines and approves features, the Tech Lead breaks them into epics and tasks, the Delivery Manager plans sprints and tracks dates and progress. Work items are associated with sprints via \`sprint:SP-xxx\` tags. Actions support a \`dueDate\` field for schedule tracking \u2014 actions with a past due date are automatically flagged as overdue in GAR reports. Use the \`sprints\` parameter on create_action/update_action to assign actions to sprints.
|
|
15795
16006
|
|
|
15796
16007
|
- **list_meetings** / **get_meeting**: Browse and read meeting records.
|
|
15797
16008
|
- **create_meeting**: Record meetings with attendees, date, and agenda. The meeting date is required \u2014 extract it from the meeting content or ask the user if not found.
|
|
@@ -15806,10 +16017,10 @@ var genericAgilePlugin = {
|
|
|
15806
16017
|
};
|
|
15807
16018
|
|
|
15808
16019
|
// src/plugins/builtin/tools/use-cases.ts
|
|
15809
|
-
import { tool as
|
|
16020
|
+
import { tool as tool9 } from "@anthropic-ai/claude-agent-sdk";
|
|
15810
16021
|
function createUseCaseTools(store) {
|
|
15811
16022
|
return [
|
|
15812
|
-
|
|
16023
|
+
tool9(
|
|
15813
16024
|
"list_use_cases",
|
|
15814
16025
|
"List all extension use cases, optionally filtered by status or extension type",
|
|
15815
16026
|
{
|
|
@@ -15839,7 +16050,7 @@ function createUseCaseTools(store) {
|
|
|
15839
16050
|
},
|
|
15840
16051
|
{ annotations: { readOnlyHint: true } }
|
|
15841
16052
|
),
|
|
15842
|
-
|
|
16053
|
+
tool9(
|
|
15843
16054
|
"get_use_case",
|
|
15844
16055
|
"Get the full content of a specific use case by ID",
|
|
15845
16056
|
{ id: external_exports.string().describe("Use case ID (e.g. 'UC-001')") },
|
|
@@ -15866,7 +16077,7 @@ function createUseCaseTools(store) {
|
|
|
15866
16077
|
},
|
|
15867
16078
|
{ annotations: { readOnlyHint: true } }
|
|
15868
16079
|
),
|
|
15869
|
-
|
|
16080
|
+
tool9(
|
|
15870
16081
|
"create_use_case",
|
|
15871
16082
|
"Create a new extension use case definition (Phase 1: Assess Extension Use Case)",
|
|
15872
16083
|
{
|
|
@@ -15900,7 +16111,7 @@ function createUseCaseTools(store) {
|
|
|
15900
16111
|
};
|
|
15901
16112
|
}
|
|
15902
16113
|
),
|
|
15903
|
-
|
|
16114
|
+
tool9(
|
|
15904
16115
|
"update_use_case",
|
|
15905
16116
|
"Update an existing extension use case",
|
|
15906
16117
|
{
|
|
@@ -15930,10 +16141,10 @@ function createUseCaseTools(store) {
|
|
|
15930
16141
|
}
|
|
15931
16142
|
|
|
15932
16143
|
// src/plugins/builtin/tools/tech-assessments.ts
|
|
15933
|
-
import { tool as
|
|
16144
|
+
import { tool as tool10 } from "@anthropic-ai/claude-agent-sdk";
|
|
15934
16145
|
function createTechAssessmentTools(store) {
|
|
15935
16146
|
return [
|
|
15936
|
-
|
|
16147
|
+
tool10(
|
|
15937
16148
|
"list_tech_assessments",
|
|
15938
16149
|
"List all technology assessments, optionally filtered by status",
|
|
15939
16150
|
{
|
|
@@ -15964,7 +16175,7 @@ function createTechAssessmentTools(store) {
|
|
|
15964
16175
|
},
|
|
15965
16176
|
{ annotations: { readOnlyHint: true } }
|
|
15966
16177
|
),
|
|
15967
|
-
|
|
16178
|
+
tool10(
|
|
15968
16179
|
"get_tech_assessment",
|
|
15969
16180
|
"Get the full content of a specific technology assessment by ID",
|
|
15970
16181
|
{ id: external_exports.string().describe("Tech assessment ID (e.g. 'TA-001')") },
|
|
@@ -15991,7 +16202,7 @@ function createTechAssessmentTools(store) {
|
|
|
15991
16202
|
},
|
|
15992
16203
|
{ annotations: { readOnlyHint: true } }
|
|
15993
16204
|
),
|
|
15994
|
-
|
|
16205
|
+
tool10(
|
|
15995
16206
|
"create_tech_assessment",
|
|
15996
16207
|
"Create a new technology assessment linked to an assessed/approved use case (Phase 2: Assess Extension Technology)",
|
|
15997
16208
|
{
|
|
@@ -16062,7 +16273,7 @@ function createTechAssessmentTools(store) {
|
|
|
16062
16273
|
};
|
|
16063
16274
|
}
|
|
16064
16275
|
),
|
|
16065
|
-
|
|
16276
|
+
tool10(
|
|
16066
16277
|
"update_tech_assessment",
|
|
16067
16278
|
"Update an existing technology assessment. The linked use case cannot be changed.",
|
|
16068
16279
|
{
|
|
@@ -16092,10 +16303,10 @@ function createTechAssessmentTools(store) {
|
|
|
16092
16303
|
}
|
|
16093
16304
|
|
|
16094
16305
|
// src/plugins/builtin/tools/extension-designs.ts
|
|
16095
|
-
import { tool as
|
|
16306
|
+
import { tool as tool11 } from "@anthropic-ai/claude-agent-sdk";
|
|
16096
16307
|
function createExtensionDesignTools(store) {
|
|
16097
16308
|
return [
|
|
16098
|
-
|
|
16309
|
+
tool11(
|
|
16099
16310
|
"list_extension_designs",
|
|
16100
16311
|
"List all extension designs, optionally filtered by status",
|
|
16101
16312
|
{
|
|
@@ -16125,7 +16336,7 @@ function createExtensionDesignTools(store) {
|
|
|
16125
16336
|
},
|
|
16126
16337
|
{ annotations: { readOnlyHint: true } }
|
|
16127
16338
|
),
|
|
16128
|
-
|
|
16339
|
+
tool11(
|
|
16129
16340
|
"get_extension_design",
|
|
16130
16341
|
"Get the full content of a specific extension design by ID",
|
|
16131
16342
|
{ id: external_exports.string().describe("Extension design ID (e.g. 'XD-001')") },
|
|
@@ -16152,7 +16363,7 @@ function createExtensionDesignTools(store) {
|
|
|
16152
16363
|
},
|
|
16153
16364
|
{ annotations: { readOnlyHint: true } }
|
|
16154
16365
|
),
|
|
16155
|
-
|
|
16366
|
+
tool11(
|
|
16156
16367
|
"create_extension_design",
|
|
16157
16368
|
"Create a new extension design linked to a recommended tech assessment (Phase 3: Define Extension Target Solution)",
|
|
16158
16369
|
{
|
|
@@ -16220,7 +16431,7 @@ function createExtensionDesignTools(store) {
|
|
|
16220
16431
|
};
|
|
16221
16432
|
}
|
|
16222
16433
|
),
|
|
16223
|
-
|
|
16434
|
+
tool11(
|
|
16224
16435
|
"update_extension_design",
|
|
16225
16436
|
"Update an existing extension design. The linked tech assessment cannot be changed.",
|
|
16226
16437
|
{
|
|
@@ -16249,10 +16460,10 @@ function createExtensionDesignTools(store) {
|
|
|
16249
16460
|
}
|
|
16250
16461
|
|
|
16251
16462
|
// src/plugins/builtin/tools/aem-reports.ts
|
|
16252
|
-
import { tool as
|
|
16463
|
+
import { tool as tool12 } from "@anthropic-ai/claude-agent-sdk";
|
|
16253
16464
|
function createAemReportTools(store) {
|
|
16254
16465
|
return [
|
|
16255
|
-
|
|
16466
|
+
tool12(
|
|
16256
16467
|
"generate_extension_portfolio",
|
|
16257
16468
|
"Generate a portfolio view of all use cases with their linked tech assessments and extension designs",
|
|
16258
16469
|
{},
|
|
@@ -16304,7 +16515,7 @@ function createAemReportTools(store) {
|
|
|
16304
16515
|
},
|
|
16305
16516
|
{ annotations: { readOnlyHint: true } }
|
|
16306
16517
|
),
|
|
16307
|
-
|
|
16518
|
+
tool12(
|
|
16308
16519
|
"generate_tech_readiness",
|
|
16309
16520
|
"Generate a BTP technology readiness report showing service coverage and gaps across assessments",
|
|
16310
16521
|
{},
|
|
@@ -16356,7 +16567,7 @@ function createAemReportTools(store) {
|
|
|
16356
16567
|
},
|
|
16357
16568
|
{ annotations: { readOnlyHint: true } }
|
|
16358
16569
|
),
|
|
16359
|
-
|
|
16570
|
+
tool12(
|
|
16360
16571
|
"generate_phase_status",
|
|
16361
16572
|
"Generate a phase progress report showing artifact counts and readiness per AEM phase",
|
|
16362
16573
|
{},
|
|
@@ -16418,11 +16629,11 @@ function createAemReportTools(store) {
|
|
|
16418
16629
|
import * as fs3 from "fs";
|
|
16419
16630
|
import * as path3 from "path";
|
|
16420
16631
|
import * as YAML2 from "yaml";
|
|
16421
|
-
import { tool as
|
|
16632
|
+
import { tool as tool13 } from "@anthropic-ai/claude-agent-sdk";
|
|
16422
16633
|
var PHASES = ["assess-use-case", "assess-technology", "define-solution"];
|
|
16423
16634
|
function createAemPhaseTools(store, marvinDir) {
|
|
16424
16635
|
return [
|
|
16425
|
-
|
|
16636
|
+
tool13(
|
|
16426
16637
|
"get_current_phase",
|
|
16427
16638
|
"Get the current AEM phase from project configuration",
|
|
16428
16639
|
{},
|
|
@@ -16443,7 +16654,7 @@ function createAemPhaseTools(store, marvinDir) {
|
|
|
16443
16654
|
},
|
|
16444
16655
|
{ annotations: { readOnlyHint: true } }
|
|
16445
16656
|
),
|
|
16446
|
-
|
|
16657
|
+
tool13(
|
|
16447
16658
|
"advance_phase",
|
|
16448
16659
|
"Advance to the next AEM phase. Performs soft gate checks and warns if artifacts are incomplete, but does not block.",
|
|
16449
16660
|
{
|
|
@@ -16952,7 +17163,7 @@ var deliveryManager = {
|
|
|
16952
17163
|
"Epic scheduling and tracking",
|
|
16953
17164
|
"Sprint planning and tracking"
|
|
16954
17165
|
],
|
|
16955
|
-
documentTypes: ["action", "decision", "meeting", "question", "feature", "epic", "sprint"],
|
|
17166
|
+
documentTypes: ["action", "decision", "meeting", "question", "feature", "epic", "task", "sprint"],
|
|
16956
17167
|
contributionTypes: ["risk-finding", "blocker-report", "dependency-update", "status-assessment"]
|
|
16957
17168
|
};
|
|
16958
17169
|
|
|
@@ -16990,9 +17201,10 @@ var techLead = {
|
|
|
16990
17201
|
"Implementation guidance",
|
|
16991
17202
|
"Non-functional requirements",
|
|
16992
17203
|
"Epic creation and scoping",
|
|
17204
|
+
"Task creation and breakdown",
|
|
16993
17205
|
"Sprint scoping and technical execution"
|
|
16994
17206
|
],
|
|
16995
|
-
documentTypes: ["decision", "action", "question", "epic", "sprint"],
|
|
17207
|
+
documentTypes: ["decision", "action", "question", "epic", "task", "sprint"],
|
|
16996
17208
|
contributionTypes: ["action-result", "spike-findings", "technical-assessment", "architecture-review"]
|
|
16997
17209
|
};
|
|
16998
17210
|
|
|
@@ -17025,8 +17237,8 @@ function resolvePersonaId(input4) {
|
|
|
17025
17237
|
}
|
|
17026
17238
|
|
|
17027
17239
|
// src/agent/session.ts
|
|
17028
|
-
import * as
|
|
17029
|
-
import * as
|
|
17240
|
+
import * as fs11 from "fs";
|
|
17241
|
+
import * as path11 from "path";
|
|
17030
17242
|
import * as readline from "readline";
|
|
17031
17243
|
import chalk2 from "chalk";
|
|
17032
17244
|
import ora from "ora";
|
|
@@ -17394,10 +17606,10 @@ import {
|
|
|
17394
17606
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
17395
17607
|
|
|
17396
17608
|
// src/agent/tools/decisions.ts
|
|
17397
|
-
import { tool as
|
|
17609
|
+
import { tool as tool14 } from "@anthropic-ai/claude-agent-sdk";
|
|
17398
17610
|
function createDecisionTools(store) {
|
|
17399
17611
|
return [
|
|
17400
|
-
|
|
17612
|
+
tool14(
|
|
17401
17613
|
"list_decisions",
|
|
17402
17614
|
"List all decisions in the project, optionally filtered by status",
|
|
17403
17615
|
{ status: external_exports.string().optional().describe("Filter by status (e.g. 'open', 'decided', 'superseded')") },
|
|
@@ -17415,7 +17627,7 @@ function createDecisionTools(store) {
|
|
|
17415
17627
|
},
|
|
17416
17628
|
{ annotations: { readOnlyHint: true } }
|
|
17417
17629
|
),
|
|
17418
|
-
|
|
17630
|
+
tool14(
|
|
17419
17631
|
"get_decision",
|
|
17420
17632
|
"Get the full content of a specific decision by ID",
|
|
17421
17633
|
{ id: external_exports.string().describe("Decision ID (e.g. 'D-001')") },
|
|
@@ -17442,7 +17654,7 @@ function createDecisionTools(store) {
|
|
|
17442
17654
|
},
|
|
17443
17655
|
{ annotations: { readOnlyHint: true } }
|
|
17444
17656
|
),
|
|
17445
|
-
|
|
17657
|
+
tool14(
|
|
17446
17658
|
"create_decision",
|
|
17447
17659
|
"Create a new decision record",
|
|
17448
17660
|
{
|
|
@@ -17473,7 +17685,7 @@ function createDecisionTools(store) {
|
|
|
17473
17685
|
};
|
|
17474
17686
|
}
|
|
17475
17687
|
),
|
|
17476
|
-
|
|
17688
|
+
tool14(
|
|
17477
17689
|
"update_decision",
|
|
17478
17690
|
"Update an existing decision",
|
|
17479
17691
|
{
|
|
@@ -17501,7 +17713,7 @@ function createDecisionTools(store) {
|
|
|
17501
17713
|
}
|
|
17502
17714
|
|
|
17503
17715
|
// src/agent/tools/actions.ts
|
|
17504
|
-
import { tool as
|
|
17716
|
+
import { tool as tool15 } from "@anthropic-ai/claude-agent-sdk";
|
|
17505
17717
|
function findMatchingSprints(store, dueDate) {
|
|
17506
17718
|
const sprints = store.list({ type: "sprint" });
|
|
17507
17719
|
return sprints.filter((s) => {
|
|
@@ -17517,7 +17729,7 @@ function findMatchingSprints(store, dueDate) {
|
|
|
17517
17729
|
}
|
|
17518
17730
|
function createActionTools(store) {
|
|
17519
17731
|
return [
|
|
17520
|
-
|
|
17732
|
+
tool15(
|
|
17521
17733
|
"list_actions",
|
|
17522
17734
|
"List all action items in the project, optionally filtered by status or owner",
|
|
17523
17735
|
{
|
|
@@ -17549,7 +17761,7 @@ function createActionTools(store) {
|
|
|
17549
17761
|
},
|
|
17550
17762
|
{ annotations: { readOnlyHint: true } }
|
|
17551
17763
|
),
|
|
17552
|
-
|
|
17764
|
+
tool15(
|
|
17553
17765
|
"get_action",
|
|
17554
17766
|
"Get the full content of a specific action item by ID",
|
|
17555
17767
|
{ id: external_exports.string().describe("Action ID (e.g. 'A-001')") },
|
|
@@ -17576,7 +17788,7 @@ function createActionTools(store) {
|
|
|
17576
17788
|
},
|
|
17577
17789
|
{ annotations: { readOnlyHint: true } }
|
|
17578
17790
|
),
|
|
17579
|
-
|
|
17791
|
+
tool15(
|
|
17580
17792
|
"create_action",
|
|
17581
17793
|
"Create a new action item",
|
|
17582
17794
|
{
|
|
@@ -17622,7 +17834,7 @@ function createActionTools(store) {
|
|
|
17622
17834
|
};
|
|
17623
17835
|
}
|
|
17624
17836
|
),
|
|
17625
|
-
|
|
17837
|
+
tool15(
|
|
17626
17838
|
"update_action",
|
|
17627
17839
|
"Update an existing action item",
|
|
17628
17840
|
{
|
|
@@ -17671,7 +17883,7 @@ function createActionTools(store) {
|
|
|
17671
17883
|
};
|
|
17672
17884
|
}
|
|
17673
17885
|
),
|
|
17674
|
-
|
|
17886
|
+
tool15(
|
|
17675
17887
|
"suggest_sprints_for_action",
|
|
17676
17888
|
"Suggest sprints whose date range contains the given due date. Helps assign actions to the right sprint.",
|
|
17677
17889
|
{
|
|
@@ -17704,10 +17916,10 @@ function createActionTools(store) {
|
|
|
17704
17916
|
}
|
|
17705
17917
|
|
|
17706
17918
|
// src/agent/tools/questions.ts
|
|
17707
|
-
import { tool as
|
|
17919
|
+
import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
|
|
17708
17920
|
function createQuestionTools(store) {
|
|
17709
17921
|
return [
|
|
17710
|
-
|
|
17922
|
+
tool16(
|
|
17711
17923
|
"list_questions",
|
|
17712
17924
|
"List all questions in the project, optionally filtered by status",
|
|
17713
17925
|
{
|
|
@@ -17728,7 +17940,7 @@ function createQuestionTools(store) {
|
|
|
17728
17940
|
},
|
|
17729
17941
|
{ annotations: { readOnlyHint: true } }
|
|
17730
17942
|
),
|
|
17731
|
-
|
|
17943
|
+
tool16(
|
|
17732
17944
|
"get_question",
|
|
17733
17945
|
"Get the full content of a specific question by ID",
|
|
17734
17946
|
{ id: external_exports.string().describe("Question ID (e.g. 'Q-001')") },
|
|
@@ -17755,7 +17967,7 @@ function createQuestionTools(store) {
|
|
|
17755
17967
|
},
|
|
17756
17968
|
{ annotations: { readOnlyHint: true } }
|
|
17757
17969
|
),
|
|
17758
|
-
|
|
17970
|
+
tool16(
|
|
17759
17971
|
"create_question",
|
|
17760
17972
|
"Create a new question that needs to be answered",
|
|
17761
17973
|
{
|
|
@@ -17786,7 +17998,7 @@ function createQuestionTools(store) {
|
|
|
17786
17998
|
};
|
|
17787
17999
|
}
|
|
17788
18000
|
),
|
|
17789
|
-
|
|
18001
|
+
tool16(
|
|
17790
18002
|
"update_question",
|
|
17791
18003
|
"Update an existing question",
|
|
17792
18004
|
{
|
|
@@ -17814,10 +18026,10 @@ function createQuestionTools(store) {
|
|
|
17814
18026
|
}
|
|
17815
18027
|
|
|
17816
18028
|
// src/agent/tools/documents.ts
|
|
17817
|
-
import { tool as
|
|
18029
|
+
import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
|
|
17818
18030
|
function createDocumentTools(store) {
|
|
17819
18031
|
return [
|
|
17820
|
-
|
|
18032
|
+
tool17(
|
|
17821
18033
|
"search_documents",
|
|
17822
18034
|
"Search all project documents, optionally filtered by type, status, or tag",
|
|
17823
18035
|
{
|
|
@@ -17849,7 +18061,7 @@ function createDocumentTools(store) {
|
|
|
17849
18061
|
},
|
|
17850
18062
|
{ annotations: { readOnlyHint: true } }
|
|
17851
18063
|
),
|
|
17852
|
-
|
|
18064
|
+
tool17(
|
|
17853
18065
|
"read_document",
|
|
17854
18066
|
"Read the full content of any project document by ID",
|
|
17855
18067
|
{ id: external_exports.string().describe("Document ID (e.g. 'D-001', 'A-003', 'Q-002')") },
|
|
@@ -17876,7 +18088,7 @@ function createDocumentTools(store) {
|
|
|
17876
18088
|
},
|
|
17877
18089
|
{ annotations: { readOnlyHint: true } }
|
|
17878
18090
|
),
|
|
17879
|
-
|
|
18091
|
+
tool17(
|
|
17880
18092
|
"project_summary",
|
|
17881
18093
|
"Get a summary of all project documents and their counts",
|
|
17882
18094
|
{},
|
|
@@ -17908,10 +18120,10 @@ function createDocumentTools(store) {
|
|
|
17908
18120
|
}
|
|
17909
18121
|
|
|
17910
18122
|
// src/agent/tools/sources.ts
|
|
17911
|
-
import { tool as
|
|
18123
|
+
import { tool as tool18 } from "@anthropic-ai/claude-agent-sdk";
|
|
17912
18124
|
function createSourceTools(manifest) {
|
|
17913
18125
|
return [
|
|
17914
|
-
|
|
18126
|
+
tool18(
|
|
17915
18127
|
"list_sources",
|
|
17916
18128
|
"List all source documents and their processing status",
|
|
17917
18129
|
{
|
|
@@ -17941,7 +18153,7 @@ function createSourceTools(manifest) {
|
|
|
17941
18153
|
},
|
|
17942
18154
|
{ annotations: { readOnlyHint: true } }
|
|
17943
18155
|
),
|
|
17944
|
-
|
|
18156
|
+
tool18(
|
|
17945
18157
|
"get_source_info",
|
|
17946
18158
|
"Get detailed information about a specific source document",
|
|
17947
18159
|
{
|
|
@@ -17979,10 +18191,10 @@ function createSourceTools(manifest) {
|
|
|
17979
18191
|
}
|
|
17980
18192
|
|
|
17981
18193
|
// src/agent/tools/sessions.ts
|
|
17982
|
-
import { tool as
|
|
18194
|
+
import { tool as tool19 } from "@anthropic-ai/claude-agent-sdk";
|
|
17983
18195
|
function createSessionTools(store) {
|
|
17984
18196
|
return [
|
|
17985
|
-
|
|
18197
|
+
tool19(
|
|
17986
18198
|
"list_sessions",
|
|
17987
18199
|
"List all saved chat sessions, sorted by most recently used",
|
|
17988
18200
|
{
|
|
@@ -18006,7 +18218,7 @@ function createSessionTools(store) {
|
|
|
18006
18218
|
},
|
|
18007
18219
|
{ annotations: { readOnlyHint: true } }
|
|
18008
18220
|
),
|
|
18009
|
-
|
|
18221
|
+
tool19(
|
|
18010
18222
|
"get_session",
|
|
18011
18223
|
"Get details of a specific saved session by name",
|
|
18012
18224
|
{ name: external_exports.string().describe("Session name (e.g. 'jwt-auth-decision')") },
|
|
@@ -18029,7 +18241,7 @@ function createSessionTools(store) {
|
|
|
18029
18241
|
|
|
18030
18242
|
// src/agent/tools/web.ts
|
|
18031
18243
|
import * as http2 from "http";
|
|
18032
|
-
import { tool as
|
|
18244
|
+
import { tool as tool22 } from "@anthropic-ai/claude-agent-sdk";
|
|
18033
18245
|
|
|
18034
18246
|
// src/web/data.ts
|
|
18035
18247
|
function getOverviewData(store) {
|
|
@@ -18265,6 +18477,7 @@ function inline(text) {
|
|
|
18265
18477
|
function layout(opts, body) {
|
|
18266
18478
|
const topItems = [
|
|
18267
18479
|
{ href: "/", label: "Overview" },
|
|
18480
|
+
{ href: "/timeline", label: "Timeline" },
|
|
18268
18481
|
{ href: "/board", label: "Board" },
|
|
18269
18482
|
{ href: "/gar", label: "GAR Report" },
|
|
18270
18483
|
{ href: "/health", label: "Health" }
|
|
@@ -18301,7 +18514,7 @@ function layout(opts, body) {
|
|
|
18301
18514
|
${groupsHtml}
|
|
18302
18515
|
</nav>
|
|
18303
18516
|
</aside>
|
|
18304
|
-
<main class="main">
|
|
18517
|
+
<main class="main${opts.mainClass ? ` ${opts.mainClass}` : ""}">
|
|
18305
18518
|
<button class="expand-toggle" onclick="document.querySelector('.main').classList.toggle('expanded')" title="Toggle wide view">
|
|
18306
18519
|
<svg class="icon-expand" viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M1 1h5v1.5H3.56l3.72 3.72-1.06 1.06L2.5 3.56V6H1V1zm14 14h-5v-1.5h2.44l-3.72-3.72 1.06-1.06 3.72 3.72V10H15v5z"/></svg>
|
|
18307
18520
|
<svg class="icon-collapse" viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M6 7H1V5.5h2.44L0.22 2.28l1.06-1.06L4.5 4.44V2H6v5zm4-1h5v1.5h-2.44l3.22 3.22-1.06 1.06L11.5 8.56V11H10V6z"/></svg>
|
|
@@ -18310,7 +18523,36 @@ function layout(opts, body) {
|
|
|
18310
18523
|
</main>
|
|
18311
18524
|
</div>
|
|
18312
18525
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
18313
|
-
<script>mermaid.initialize({
|
|
18526
|
+
<script>mermaid.initialize({
|
|
18527
|
+
startOnLoad: true,
|
|
18528
|
+
theme: 'dark',
|
|
18529
|
+
themeVariables: {
|
|
18530
|
+
background: '#1a1d27',
|
|
18531
|
+
primaryColor: '#2a2e3a',
|
|
18532
|
+
sectionBkgColor: '#1a1d27',
|
|
18533
|
+
sectionBkgColor2: '#222632',
|
|
18534
|
+
altSectionBkgColor: '#222632',
|
|
18535
|
+
gridColor: '#2a2e3a',
|
|
18536
|
+
taskBorderColor: '#475569',
|
|
18537
|
+
doneTaskBkgColor: '#065f46',
|
|
18538
|
+
doneTaskBorderColor: '#34d399',
|
|
18539
|
+
activeTaskBkgColor: '#78350f',
|
|
18540
|
+
activeTaskBorderColor: '#fbbf24',
|
|
18541
|
+
taskTextColor: '#e1e4ea',
|
|
18542
|
+
sectionBkgColor: '#1a1d27',
|
|
18543
|
+
pie1: '#34d399',
|
|
18544
|
+
pie2: '#475569',
|
|
18545
|
+
pie3: '#fbbf24',
|
|
18546
|
+
pie4: '#f87171',
|
|
18547
|
+
pie5: '#6c8cff',
|
|
18548
|
+
pie6: '#a78bfa',
|
|
18549
|
+
pie7: '#f472b6',
|
|
18550
|
+
pieTitleTextColor: '#e1e4ea',
|
|
18551
|
+
pieSectionTextColor: '#e1e4ea',
|
|
18552
|
+
pieLegendTextColor: '#e1e4ea',
|
|
18553
|
+
pieStrokeColor: '#1a1d27'
|
|
18554
|
+
}
|
|
18555
|
+
});</script>
|
|
18314
18556
|
</body>
|
|
18315
18557
|
</html>`;
|
|
18316
18558
|
}
|
|
@@ -18557,6 +18799,10 @@ a:hover { text-decoration: underline; }
|
|
|
18557
18799
|
/* Table */
|
|
18558
18800
|
.table-wrap {
|
|
18559
18801
|
overflow-x: auto;
|
|
18802
|
+
overflow-y: auto;
|
|
18803
|
+
max-height: calc(100vh - 280px);
|
|
18804
|
+
border: 1px solid var(--border);
|
|
18805
|
+
border-radius: var(--radius);
|
|
18560
18806
|
}
|
|
18561
18807
|
|
|
18562
18808
|
table {
|
|
@@ -18572,6 +18818,10 @@ th {
|
|
|
18572
18818
|
letter-spacing: 0.05em;
|
|
18573
18819
|
color: var(--text-dim);
|
|
18574
18820
|
border-bottom: 1px solid var(--border);
|
|
18821
|
+
position: sticky;
|
|
18822
|
+
top: 0;
|
|
18823
|
+
background: var(--bg-card);
|
|
18824
|
+
z-index: 1;
|
|
18575
18825
|
}
|
|
18576
18826
|
|
|
18577
18827
|
td {
|
|
@@ -18619,6 +18869,8 @@ tr:hover td {
|
|
|
18619
18869
|
border: 1px solid var(--border);
|
|
18620
18870
|
border-radius: var(--radius);
|
|
18621
18871
|
padding: 1.25rem;
|
|
18872
|
+
display: flex;
|
|
18873
|
+
flex-direction: column;
|
|
18622
18874
|
}
|
|
18623
18875
|
|
|
18624
18876
|
.gar-area .area-header {
|
|
@@ -18649,6 +18901,9 @@ tr:hover td {
|
|
|
18649
18901
|
.gar-area ul {
|
|
18650
18902
|
list-style: none;
|
|
18651
18903
|
font-size: 0.8rem;
|
|
18904
|
+
max-height: 200px;
|
|
18905
|
+
overflow-y: auto;
|
|
18906
|
+
scrollbar-width: thin;
|
|
18652
18907
|
}
|
|
18653
18908
|
|
|
18654
18909
|
.gar-area li {
|
|
@@ -18671,13 +18926,14 @@ tr:hover td {
|
|
|
18671
18926
|
display: flex;
|
|
18672
18927
|
gap: 1rem;
|
|
18673
18928
|
overflow-x: auto;
|
|
18929
|
+
scrollbar-width: thin;
|
|
18674
18930
|
padding-bottom: 1rem;
|
|
18675
18931
|
}
|
|
18676
18932
|
|
|
18677
18933
|
.board-column {
|
|
18678
18934
|
min-width: 240px;
|
|
18679
18935
|
max-width: 300px;
|
|
18680
|
-
flex:
|
|
18936
|
+
flex: 0 0 auto;
|
|
18681
18937
|
}
|
|
18682
18938
|
|
|
18683
18939
|
.board-column-header {
|
|
@@ -18690,6 +18946,7 @@ tr:hover td {
|
|
|
18690
18946
|
margin-bottom: 0.5rem;
|
|
18691
18947
|
display: flex;
|
|
18692
18948
|
justify-content: space-between;
|
|
18949
|
+
flex-shrink: 0;
|
|
18693
18950
|
}
|
|
18694
18951
|
|
|
18695
18952
|
.board-column-header .count {
|
|
@@ -18871,6 +19128,241 @@ tr:hover td {
|
|
|
18871
19128
|
.mermaid-row .mermaid-container {
|
|
18872
19129
|
margin: 0;
|
|
18873
19130
|
}
|
|
19131
|
+
|
|
19132
|
+
/* Three-column artifact flow */
|
|
19133
|
+
.flow-diagram {
|
|
19134
|
+
background: var(--bg-card);
|
|
19135
|
+
border: 1px solid var(--border);
|
|
19136
|
+
border-radius: var(--radius);
|
|
19137
|
+
padding: 1.25rem;
|
|
19138
|
+
position: relative;
|
|
19139
|
+
overflow-x: auto;
|
|
19140
|
+
}
|
|
19141
|
+
|
|
19142
|
+
.flow-lines {
|
|
19143
|
+
position: absolute;
|
|
19144
|
+
top: 0;
|
|
19145
|
+
left: 0;
|
|
19146
|
+
pointer-events: none;
|
|
19147
|
+
}
|
|
19148
|
+
|
|
19149
|
+
.flow-columns {
|
|
19150
|
+
display: flex;
|
|
19151
|
+
gap: 3rem;
|
|
19152
|
+
position: relative;
|
|
19153
|
+
min-width: 600px;
|
|
19154
|
+
}
|
|
19155
|
+
|
|
19156
|
+
.flow-column {
|
|
19157
|
+
flex: 1;
|
|
19158
|
+
min-width: 0;
|
|
19159
|
+
display: flex;
|
|
19160
|
+
flex-direction: column;
|
|
19161
|
+
gap: 0.5rem;
|
|
19162
|
+
}
|
|
19163
|
+
|
|
19164
|
+
.flow-column-header {
|
|
19165
|
+
font-size: 0.7rem;
|
|
19166
|
+
text-transform: uppercase;
|
|
19167
|
+
letter-spacing: 0.06em;
|
|
19168
|
+
color: var(--text-dim);
|
|
19169
|
+
font-weight: 600;
|
|
19170
|
+
padding-bottom: 0.4rem;
|
|
19171
|
+
border-bottom: 1px solid var(--border);
|
|
19172
|
+
margin-bottom: 0.25rem;
|
|
19173
|
+
}
|
|
19174
|
+
|
|
19175
|
+
.flow-node {
|
|
19176
|
+
padding: 0.5rem 0.65rem;
|
|
19177
|
+
border-radius: 6px;
|
|
19178
|
+
border-left: 3px solid var(--border);
|
|
19179
|
+
background: var(--bg);
|
|
19180
|
+
transition: border-color 0.15s, background 0.15s;
|
|
19181
|
+
}
|
|
19182
|
+
|
|
19183
|
+
.flow-node:hover {
|
|
19184
|
+
background: var(--bg-hover);
|
|
19185
|
+
}
|
|
19186
|
+
|
|
19187
|
+
.flow-node-id {
|
|
19188
|
+
display: inline-block;
|
|
19189
|
+
font-family: var(--mono);
|
|
19190
|
+
font-size: 0.65rem;
|
|
19191
|
+
color: var(--accent);
|
|
19192
|
+
margin-bottom: 0.15rem;
|
|
19193
|
+
text-decoration: none;
|
|
19194
|
+
}
|
|
19195
|
+
|
|
19196
|
+
.flow-node-id:hover {
|
|
19197
|
+
text-decoration: underline;
|
|
19198
|
+
}
|
|
19199
|
+
|
|
19200
|
+
.flow-node-title {
|
|
19201
|
+
display: block;
|
|
19202
|
+
font-size: 0.8rem;
|
|
19203
|
+
}
|
|
19204
|
+
|
|
19205
|
+
.flow-done { border-left-color: var(--green); }
|
|
19206
|
+
.flow-active { border-left-color: var(--amber); }
|
|
19207
|
+
.flow-blocked { border-left-color: var(--red); }
|
|
19208
|
+
.flow-default { border-left-color: var(--accent-dim); }
|
|
19209
|
+
|
|
19210
|
+
.flow-node { cursor: pointer; transition: opacity 0.2s, border-color 0.15s, background 0.15s; }
|
|
19211
|
+
.flow-dim { opacity: 0.2; }
|
|
19212
|
+
.flow-lit { background: var(--bg-hover); }
|
|
19213
|
+
.flow-line-lit { stroke: var(--accent) !important; stroke-width: 2 !important; }
|
|
19214
|
+
.flow-line-dim { opacity: 0.08; }
|
|
19215
|
+
|
|
19216
|
+
/* Gantt truncation note */
|
|
19217
|
+
.mermaid-note {
|
|
19218
|
+
font-size: 0.75rem;
|
|
19219
|
+
color: var(--text-dim);
|
|
19220
|
+
text-align: right;
|
|
19221
|
+
margin-bottom: 0.5rem;
|
|
19222
|
+
}
|
|
19223
|
+
|
|
19224
|
+
/* HTML Gantt chart */
|
|
19225
|
+
.gantt {
|
|
19226
|
+
background: var(--bg-card);
|
|
19227
|
+
border: 1px solid var(--border);
|
|
19228
|
+
border-radius: var(--radius);
|
|
19229
|
+
padding: 1.25rem 1.25rem 1.25rem 0;
|
|
19230
|
+
position: relative;
|
|
19231
|
+
overflow-x: auto;
|
|
19232
|
+
}
|
|
19233
|
+
|
|
19234
|
+
.gantt-chart {
|
|
19235
|
+
min-width: 600px;
|
|
19236
|
+
}
|
|
19237
|
+
|
|
19238
|
+
.gantt-overlay {
|
|
19239
|
+
position: absolute;
|
|
19240
|
+
top: 0;
|
|
19241
|
+
left: 0;
|
|
19242
|
+
right: 0;
|
|
19243
|
+
bottom: 0;
|
|
19244
|
+
pointer-events: none;
|
|
19245
|
+
display: flex;
|
|
19246
|
+
}
|
|
19247
|
+
|
|
19248
|
+
.gantt-header,
|
|
19249
|
+
.gantt-section-row,
|
|
19250
|
+
.gantt-row,
|
|
19251
|
+
.gantt-overlay {
|
|
19252
|
+
display: flex;
|
|
19253
|
+
align-items: center;
|
|
19254
|
+
}
|
|
19255
|
+
|
|
19256
|
+
.gantt-label {
|
|
19257
|
+
width: 200px;
|
|
19258
|
+
min-width: 200px;
|
|
19259
|
+
padding: 0.3rem 0.75rem;
|
|
19260
|
+
font-size: 0.8rem;
|
|
19261
|
+
color: var(--text-dim);
|
|
19262
|
+
text-align: right;
|
|
19263
|
+
white-space: nowrap;
|
|
19264
|
+
overflow: hidden;
|
|
19265
|
+
text-overflow: ellipsis;
|
|
19266
|
+
}
|
|
19267
|
+
|
|
19268
|
+
.gantt-section-label {
|
|
19269
|
+
font-weight: 600;
|
|
19270
|
+
color: var(--text);
|
|
19271
|
+
font-size: 0.75rem;
|
|
19272
|
+
text-transform: uppercase;
|
|
19273
|
+
letter-spacing: 0.03em;
|
|
19274
|
+
padding-top: 0.6rem;
|
|
19275
|
+
}
|
|
19276
|
+
|
|
19277
|
+
.gantt-track {
|
|
19278
|
+
flex: 1;
|
|
19279
|
+
position: relative;
|
|
19280
|
+
height: 28px;
|
|
19281
|
+
min-width: 0;
|
|
19282
|
+
}
|
|
19283
|
+
|
|
19284
|
+
.gantt-section-row .gantt-track {
|
|
19285
|
+
height: 20px;
|
|
19286
|
+
}
|
|
19287
|
+
|
|
19288
|
+
.gantt-section-bg {
|
|
19289
|
+
position: absolute;
|
|
19290
|
+
top: 0;
|
|
19291
|
+
bottom: 0;
|
|
19292
|
+
background: var(--bg-hover);
|
|
19293
|
+
border-radius: 3px;
|
|
19294
|
+
opacity: 0.4;
|
|
19295
|
+
}
|
|
19296
|
+
|
|
19297
|
+
.gantt-bar {
|
|
19298
|
+
position: absolute;
|
|
19299
|
+
top: 4px;
|
|
19300
|
+
bottom: 4px;
|
|
19301
|
+
border-radius: 4px;
|
|
19302
|
+
min-width: 6px;
|
|
19303
|
+
transition: opacity 0.15s;
|
|
19304
|
+
}
|
|
19305
|
+
|
|
19306
|
+
.gantt-bar:hover {
|
|
19307
|
+
opacity: 0.85;
|
|
19308
|
+
}
|
|
19309
|
+
|
|
19310
|
+
.gantt-bar-done {
|
|
19311
|
+
background: var(--green);
|
|
19312
|
+
}
|
|
19313
|
+
|
|
19314
|
+
.gantt-bar-active {
|
|
19315
|
+
background: var(--amber);
|
|
19316
|
+
}
|
|
19317
|
+
|
|
19318
|
+
.gantt-bar-blocked {
|
|
19319
|
+
background: var(--red);
|
|
19320
|
+
}
|
|
19321
|
+
|
|
19322
|
+
.gantt-bar-default {
|
|
19323
|
+
background: var(--accent-dim);
|
|
19324
|
+
}
|
|
19325
|
+
|
|
19326
|
+
.gantt-dates {
|
|
19327
|
+
height: 24px;
|
|
19328
|
+
border-bottom: 1px solid var(--border);
|
|
19329
|
+
margin-bottom: 0.25rem;
|
|
19330
|
+
}
|
|
19331
|
+
|
|
19332
|
+
.gantt-marker {
|
|
19333
|
+
position: absolute;
|
|
19334
|
+
top: 0;
|
|
19335
|
+
bottom: 0;
|
|
19336
|
+
border-left: 1px solid var(--border);
|
|
19337
|
+
}
|
|
19338
|
+
|
|
19339
|
+
.gantt-marker span {
|
|
19340
|
+
position: absolute;
|
|
19341
|
+
top: 2px;
|
|
19342
|
+
left: 6px;
|
|
19343
|
+
font-size: 0.65rem;
|
|
19344
|
+
color: var(--text-dim);
|
|
19345
|
+
white-space: nowrap;
|
|
19346
|
+
}
|
|
19347
|
+
|
|
19348
|
+
.gantt-today {
|
|
19349
|
+
position: absolute;
|
|
19350
|
+
top: 0;
|
|
19351
|
+
bottom: 0;
|
|
19352
|
+
width: 2px;
|
|
19353
|
+
background: var(--red);
|
|
19354
|
+
opacity: 0.7;
|
|
19355
|
+
}
|
|
19356
|
+
|
|
19357
|
+
/* Pie chart color overrides */
|
|
19358
|
+
.mermaid-container .pieCircle {
|
|
19359
|
+
stroke: var(--bg-card);
|
|
19360
|
+
}
|
|
19361
|
+
|
|
19362
|
+
.mermaid-container text.slice {
|
|
19363
|
+
fill: var(--bg) !important;
|
|
19364
|
+
font-weight: 600;
|
|
19365
|
+
}
|
|
18874
19366
|
`;
|
|
18875
19367
|
}
|
|
18876
19368
|
|
|
@@ -18879,98 +19371,275 @@ function sanitize(text, maxLen = 40) {
|
|
|
18879
19371
|
const cleaned = text.replace(/["'`]/g, "").replace(/[\r\n]+/g, " ");
|
|
18880
19372
|
return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 1) + "\u2026" : cleaned;
|
|
18881
19373
|
}
|
|
18882
|
-
function mermaidBlock(definition) {
|
|
18883
|
-
|
|
19374
|
+
function mermaidBlock(definition, extraClass) {
|
|
19375
|
+
const cls = ["mermaid-container", extraClass].filter(Boolean).join(" ");
|
|
19376
|
+
return `<div class="${cls}"><pre class="mermaid">
|
|
18884
19377
|
${definition}
|
|
18885
19378
|
</pre></div>`;
|
|
18886
19379
|
}
|
|
18887
19380
|
function placeholder(message) {
|
|
18888
19381
|
return `<div class="mermaid-container mermaid-empty"><p>${message}</p></div>`;
|
|
18889
19382
|
}
|
|
18890
|
-
function
|
|
18891
|
-
|
|
19383
|
+
function toMs(date5) {
|
|
19384
|
+
return (/* @__PURE__ */ new Date(date5 + "T00:00:00")).getTime();
|
|
19385
|
+
}
|
|
19386
|
+
function fmtDate(ms) {
|
|
19387
|
+
const d = new Date(ms);
|
|
19388
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
19389
|
+
return `${months[d.getMonth()]} ${d.getDate()}`;
|
|
19390
|
+
}
|
|
19391
|
+
function buildTimelineGantt(data, maxSprints = 6) {
|
|
19392
|
+
const sprintsWithDates = data.sprints.filter((s) => s.startDate && s.endDate).sort((a, b) => a.startDate < b.startDate ? -1 : 1);
|
|
18892
19393
|
if (sprintsWithDates.length === 0) {
|
|
18893
19394
|
return placeholder("No timeline data available \u2014 sprints need start and end dates.");
|
|
18894
19395
|
}
|
|
19396
|
+
const truncated = sprintsWithDates.length > maxSprints;
|
|
19397
|
+
const visibleSprints = truncated ? sprintsWithDates.slice(-maxSprints) : sprintsWithDates;
|
|
19398
|
+
const hiddenCount = sprintsWithDates.length - visibleSprints.length;
|
|
18895
19399
|
const epicMap = new Map(data.epics.map((e) => [e.id, e]));
|
|
18896
|
-
const
|
|
18897
|
-
|
|
18898
|
-
|
|
19400
|
+
const allStarts = visibleSprints.map((s) => toMs(s.startDate));
|
|
19401
|
+
const allEnds = visibleSprints.map((s) => toMs(s.endDate));
|
|
19402
|
+
const timelineStart = Math.min(...allStarts);
|
|
19403
|
+
const timelineEnd = Math.max(...allEnds);
|
|
19404
|
+
const span = timelineEnd - timelineStart || 1;
|
|
19405
|
+
const pct = (ms) => (ms - timelineStart) / span * 100;
|
|
19406
|
+
const DAY = 864e5;
|
|
19407
|
+
const markers = [];
|
|
19408
|
+
let tick = timelineStart;
|
|
19409
|
+
const startDay = new Date(tick).getDay();
|
|
19410
|
+
tick += (8 - startDay) % 7 * DAY;
|
|
19411
|
+
while (tick <= timelineEnd) {
|
|
19412
|
+
const left = pct(tick);
|
|
19413
|
+
markers.push(
|
|
19414
|
+
`<div class="gantt-marker" style="left:${left.toFixed(2)}%"><span>${fmtDate(tick)}</span></div>`
|
|
19415
|
+
);
|
|
19416
|
+
tick += 7 * DAY;
|
|
19417
|
+
}
|
|
19418
|
+
const now = Date.now();
|
|
19419
|
+
let todayMarker = "";
|
|
19420
|
+
if (now >= timelineStart && now <= timelineEnd) {
|
|
19421
|
+
todayMarker = `<div class="gantt-today" style="left:${pct(now).toFixed(2)}%"></div>`;
|
|
19422
|
+
}
|
|
19423
|
+
const rows = [];
|
|
19424
|
+
for (const sprint of visibleSprints) {
|
|
19425
|
+
const sStart = toMs(sprint.startDate);
|
|
19426
|
+
const sEnd = toMs(sprint.endDate);
|
|
19427
|
+
rows.push(`<div class="gantt-section-row">
|
|
19428
|
+
<div class="gantt-label gantt-section-label">${sanitize(sprint.id + " " + sprint.title, 50)}</div>
|
|
19429
|
+
<div class="gantt-track">
|
|
19430
|
+
<div class="gantt-section-bg" style="left:${pct(sStart).toFixed(2)}%;width:${(pct(sEnd) - pct(sStart)).toFixed(2)}%"></div>
|
|
19431
|
+
</div>
|
|
19432
|
+
</div>`);
|
|
18899
19433
|
const linked = sprint.linkedEpics.map((eid) => epicMap.get(eid)).filter(Boolean);
|
|
18900
|
-
|
|
18901
|
-
|
|
18902
|
-
|
|
18903
|
-
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
19434
|
+
const items = linked.length > 0 ? linked.map((e) => ({ label: sanitize(e.id + " " + e.title), status: e.status })) : [{ label: sanitize(sprint.title), status: sprint.status }];
|
|
19435
|
+
for (const item of items) {
|
|
19436
|
+
const cls = item.status === "done" || item.status === "completed" ? "gantt-bar-done" : item.status === "in-progress" || item.status === "active" ? "gantt-bar-active" : item.status === "blocked" ? "gantt-bar-blocked" : "gantt-bar-default";
|
|
19437
|
+
const left = pct(sStart).toFixed(2);
|
|
19438
|
+
const width = (pct(sEnd) - pct(sStart)).toFixed(2);
|
|
19439
|
+
rows.push(`<div class="gantt-row">
|
|
19440
|
+
<div class="gantt-label">${item.label}</div>
|
|
19441
|
+
<div class="gantt-track">
|
|
19442
|
+
<div class="gantt-bar ${cls}" style="left:${left}%;width:${width}%"></div>
|
|
19443
|
+
</div>
|
|
19444
|
+
</div>`);
|
|
18907
19445
|
}
|
|
18908
19446
|
}
|
|
18909
|
-
|
|
19447
|
+
const note = truncated ? `<div class="mermaid-note">${hiddenCount} earlier sprint${hiddenCount > 1 ? "s" : ""} not shown</div>` : "";
|
|
19448
|
+
return `${note}
|
|
19449
|
+
<div class="gantt">
|
|
19450
|
+
<div class="gantt-chart">
|
|
19451
|
+
<div class="gantt-header">
|
|
19452
|
+
<div class="gantt-label"></div>
|
|
19453
|
+
<div class="gantt-track gantt-dates">${markers.join("")}</div>
|
|
19454
|
+
</div>
|
|
19455
|
+
${rows.join("\n")}
|
|
19456
|
+
</div>
|
|
19457
|
+
<div class="gantt-overlay">
|
|
19458
|
+
<div class="gantt-label"></div>
|
|
19459
|
+
<div class="gantt-track">${todayMarker}</div>
|
|
19460
|
+
</div>
|
|
19461
|
+
</div>`;
|
|
19462
|
+
}
|
|
19463
|
+
function statusClass(status) {
|
|
19464
|
+
const s = status.toLowerCase();
|
|
19465
|
+
if (s === "done" || s === "completed") return "flow-done";
|
|
19466
|
+
if (s === "in-progress" || s === "active") return "flow-active";
|
|
19467
|
+
if (s === "blocked") return "flow-blocked";
|
|
19468
|
+
return "flow-default";
|
|
18910
19469
|
}
|
|
18911
19470
|
function buildArtifactFlowchart(data) {
|
|
18912
19471
|
if (data.features.length === 0 && data.epics.length === 0) {
|
|
18913
19472
|
return placeholder("No artifact relationships found \u2014 create features and epics to see the hierarchy.");
|
|
18914
19473
|
}
|
|
18915
|
-
const
|
|
18916
|
-
|
|
18917
|
-
lines.push(" classDef inprogress fill:#78350f,stroke:#fbbf24,color:#fef3c7");
|
|
18918
|
-
lines.push(" classDef blocked fill:#7f1d1d,stroke:#f87171,color:#fee2e2");
|
|
18919
|
-
lines.push(" classDef default fill:#1e293b,stroke:#475569,color:#e2e8f0");
|
|
18920
|
-
const nodeIds = /* @__PURE__ */ new Set();
|
|
19474
|
+
const edges = [];
|
|
19475
|
+
const epicsByFeature = /* @__PURE__ */ new Map();
|
|
18921
19476
|
for (const epic of data.epics) {
|
|
18922
|
-
for (const
|
|
18923
|
-
|
|
18924
|
-
|
|
18925
|
-
|
|
18926
|
-
const eNode = epic.id.replace(/-/g, "_");
|
|
18927
|
-
if (!nodeIds.has(fNode)) {
|
|
18928
|
-
lines.push(` ${fNode}["${sanitize(feature.id + " " + feature.title)}"]`);
|
|
18929
|
-
nodeIds.add(fNode);
|
|
18930
|
-
}
|
|
18931
|
-
if (!nodeIds.has(eNode)) {
|
|
18932
|
-
lines.push(` ${eNode}["${sanitize(epic.id + " " + epic.title)}"]`);
|
|
18933
|
-
nodeIds.add(eNode);
|
|
18934
|
-
}
|
|
18935
|
-
lines.push(` ${fNode} --> ${eNode}`);
|
|
18936
|
-
}
|
|
19477
|
+
for (const fid of epic.linkedFeature) {
|
|
19478
|
+
if (!epicsByFeature.has(fid)) epicsByFeature.set(fid, []);
|
|
19479
|
+
epicsByFeature.get(fid).push(epic.id);
|
|
19480
|
+
edges.push({ from: fid, to: epic.id });
|
|
18937
19481
|
}
|
|
18938
19482
|
}
|
|
19483
|
+
const sprintsByEpic = /* @__PURE__ */ new Map();
|
|
18939
19484
|
for (const sprint of data.sprints) {
|
|
18940
|
-
const
|
|
18941
|
-
|
|
18942
|
-
|
|
18943
|
-
|
|
18944
|
-
|
|
18945
|
-
|
|
18946
|
-
|
|
18947
|
-
|
|
18948
|
-
|
|
18949
|
-
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
18954
|
-
|
|
18955
|
-
|
|
18956
|
-
|
|
18957
|
-
if (
|
|
19485
|
+
for (const eid of sprint.linkedEpics) {
|
|
19486
|
+
if (!sprintsByEpic.has(eid)) sprintsByEpic.set(eid, []);
|
|
19487
|
+
sprintsByEpic.get(eid).push(sprint.id);
|
|
19488
|
+
edges.push({ from: eid, to: sprint.id });
|
|
19489
|
+
}
|
|
19490
|
+
}
|
|
19491
|
+
const connectedFeatureIds = new Set(epicsByFeature.keys());
|
|
19492
|
+
const connectedEpicIds = /* @__PURE__ */ new Set();
|
|
19493
|
+
for (const ids of epicsByFeature.values()) ids.forEach((id) => connectedEpicIds.add(id));
|
|
19494
|
+
for (const ids of sprintsByEpic.values()) ids.forEach(() => {
|
|
19495
|
+
});
|
|
19496
|
+
for (const eid of sprintsByEpic.keys()) connectedEpicIds.add(eid);
|
|
19497
|
+
const connectedSprintIds = /* @__PURE__ */ new Set();
|
|
19498
|
+
for (const ids of sprintsByEpic.values()) ids.forEach((id) => connectedSprintIds.add(id));
|
|
19499
|
+
const features = data.features.filter((f) => connectedFeatureIds.has(f.id));
|
|
19500
|
+
const epics = data.epics.filter((e) => connectedEpicIds.has(e.id));
|
|
19501
|
+
const sprints = data.sprints.filter((s) => connectedSprintIds.has(s.id)).sort((a, b) => (a.startDate ?? "").localeCompare(b.startDate ?? ""));
|
|
19502
|
+
if (features.length === 0 && epics.length === 0) {
|
|
18958
19503
|
return placeholder("No artifact relationships found \u2014 link epics to features and sprints.");
|
|
18959
19504
|
}
|
|
18960
|
-
const
|
|
18961
|
-
|
|
18962
|
-
|
|
18963
|
-
|
|
18964
|
-
|
|
18965
|
-
|
|
18966
|
-
|
|
18967
|
-
|
|
18968
|
-
|
|
18969
|
-
|
|
18970
|
-
lines
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
19505
|
+
const renderNode = (id, title, status, type) => `<div class="flow-node ${statusClass(status)}" data-flow-id="${id}">
|
|
19506
|
+
<a class="flow-node-id" href="/docs/${type}/${id}">${id}</a>
|
|
19507
|
+
<span class="flow-node-title">${sanitize(title, 35)}</span>
|
|
19508
|
+
</div>`;
|
|
19509
|
+
const featuresHtml = features.map((f) => renderNode(f.id, f.title, f.status, "feature")).join("\n");
|
|
19510
|
+
const epicsHtml = epics.map((e) => renderNode(e.id, e.title, e.status, "epic")).join("\n");
|
|
19511
|
+
const sprintsHtml = sprints.map((s) => renderNode(s.id, s.title, s.status, "sprint")).join("\n");
|
|
19512
|
+
const edgesJson = JSON.stringify(edges);
|
|
19513
|
+
return `
|
|
19514
|
+
<div class="flow-diagram" id="flow-diagram">
|
|
19515
|
+
<svg class="flow-lines" id="flow-lines"></svg>
|
|
19516
|
+
<div class="flow-columns">
|
|
19517
|
+
<div class="flow-column">
|
|
19518
|
+
<div class="flow-column-header">Features</div>
|
|
19519
|
+
${featuresHtml}
|
|
19520
|
+
</div>
|
|
19521
|
+
<div class="flow-column">
|
|
19522
|
+
<div class="flow-column-header">Epics</div>
|
|
19523
|
+
${epicsHtml}
|
|
19524
|
+
</div>
|
|
19525
|
+
<div class="flow-column">
|
|
19526
|
+
<div class="flow-column-header">Sprints</div>
|
|
19527
|
+
${sprintsHtml}
|
|
19528
|
+
</div>
|
|
19529
|
+
</div>
|
|
19530
|
+
</div>
|
|
19531
|
+
<script>
|
|
19532
|
+
(function() {
|
|
19533
|
+
var edges = ${edgesJson};
|
|
19534
|
+
var container = document.getElementById('flow-diagram');
|
|
19535
|
+
var svg = document.getElementById('flow-lines');
|
|
19536
|
+
if (!container || !svg) return;
|
|
19537
|
+
|
|
19538
|
+
// Build adjacency map (bidirectional) for traversal
|
|
19539
|
+
var adj = {};
|
|
19540
|
+
edges.forEach(function(e) {
|
|
19541
|
+
if (!adj[e.from]) adj[e.from] = [];
|
|
19542
|
+
if (!adj[e.to]) adj[e.to] = [];
|
|
19543
|
+
adj[e.from].push(e.to);
|
|
19544
|
+
adj[e.to].push(e.from);
|
|
19545
|
+
});
|
|
19546
|
+
|
|
19547
|
+
function drawLines() {
|
|
19548
|
+
var rect = container.getBoundingClientRect();
|
|
19549
|
+
svg.setAttribute('width', rect.width);
|
|
19550
|
+
svg.setAttribute('height', rect.height);
|
|
19551
|
+
svg.innerHTML = '';
|
|
19552
|
+
|
|
19553
|
+
edges.forEach(function(edge) {
|
|
19554
|
+
var fromEl = container.querySelector('[data-flow-id="' + edge.from + '"]');
|
|
19555
|
+
var toEl = container.querySelector('[data-flow-id="' + edge.to + '"]');
|
|
19556
|
+
if (!fromEl || !toEl) return;
|
|
19557
|
+
|
|
19558
|
+
var fr = fromEl.getBoundingClientRect();
|
|
19559
|
+
var tr = toEl.getBoundingClientRect();
|
|
19560
|
+
var x1 = fr.right - rect.left;
|
|
19561
|
+
var y1 = fr.top + fr.height / 2 - rect.top;
|
|
19562
|
+
var x2 = tr.left - rect.left;
|
|
19563
|
+
var y2 = tr.top + tr.height / 2 - rect.top;
|
|
19564
|
+
var mx = (x1 + x2) / 2;
|
|
19565
|
+
|
|
19566
|
+
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
19567
|
+
path.setAttribute('d', 'M' + x1 + ',' + y1 + ' C' + mx + ',' + y1 + ' ' + mx + ',' + y2 + ' ' + x2 + ',' + y2);
|
|
19568
|
+
path.setAttribute('fill', 'none');
|
|
19569
|
+
path.setAttribute('stroke', '#2a2e3a');
|
|
19570
|
+
path.setAttribute('stroke-width', '1.5');
|
|
19571
|
+
path.dataset.from = edge.from;
|
|
19572
|
+
path.dataset.to = edge.to;
|
|
19573
|
+
svg.appendChild(path);
|
|
19574
|
+
});
|
|
19575
|
+
}
|
|
19576
|
+
|
|
19577
|
+
// Find all nodes reachable from a starting node
|
|
19578
|
+
function findConnected(startId) {
|
|
19579
|
+
var visited = {};
|
|
19580
|
+
var queue = [startId];
|
|
19581
|
+
visited[startId] = true;
|
|
19582
|
+
while (queue.length) {
|
|
19583
|
+
var id = queue.shift();
|
|
19584
|
+
(adj[id] || []).forEach(function(neighbor) {
|
|
19585
|
+
if (!visited[neighbor]) {
|
|
19586
|
+
visited[neighbor] = true;
|
|
19587
|
+
queue.push(neighbor);
|
|
19588
|
+
}
|
|
19589
|
+
});
|
|
19590
|
+
}
|
|
19591
|
+
return visited;
|
|
19592
|
+
}
|
|
19593
|
+
|
|
19594
|
+
function highlight(hoveredId) {
|
|
19595
|
+
var connected = findConnected(hoveredId);
|
|
19596
|
+
container.querySelectorAll('.flow-node').forEach(function(n) {
|
|
19597
|
+
if (connected[n.dataset.flowId]) {
|
|
19598
|
+
n.classList.add('flow-lit');
|
|
19599
|
+
n.classList.remove('flow-dim');
|
|
19600
|
+
} else {
|
|
19601
|
+
n.classList.add('flow-dim');
|
|
19602
|
+
n.classList.remove('flow-lit');
|
|
19603
|
+
}
|
|
19604
|
+
});
|
|
19605
|
+
svg.querySelectorAll('path').forEach(function(p) {
|
|
19606
|
+
if (connected[p.dataset.from] && connected[p.dataset.to]) {
|
|
19607
|
+
p.classList.add('flow-line-lit');
|
|
19608
|
+
p.classList.remove('flow-line-dim');
|
|
19609
|
+
} else {
|
|
19610
|
+
p.classList.add('flow-line-dim');
|
|
19611
|
+
p.classList.remove('flow-line-lit');
|
|
19612
|
+
}
|
|
19613
|
+
});
|
|
19614
|
+
}
|
|
19615
|
+
|
|
19616
|
+
function clearHighlight() {
|
|
19617
|
+
container.querySelectorAll('.flow-node').forEach(function(n) { n.classList.remove('flow-lit', 'flow-dim'); });
|
|
19618
|
+
svg.querySelectorAll('path').forEach(function(p) { p.classList.remove('flow-line-lit', 'flow-line-dim'); });
|
|
19619
|
+
}
|
|
19620
|
+
|
|
19621
|
+
var activeId = null;
|
|
19622
|
+
container.addEventListener('click', function(e) {
|
|
19623
|
+
// Let the ID link navigate normally
|
|
19624
|
+
if (e.target.closest('a')) return;
|
|
19625
|
+
|
|
19626
|
+
var node = e.target.closest('.flow-node');
|
|
19627
|
+
var clickedId = node ? node.dataset.flowId : null;
|
|
19628
|
+
|
|
19629
|
+
if (!clickedId || clickedId === activeId) {
|
|
19630
|
+
activeId = null;
|
|
19631
|
+
clearHighlight();
|
|
19632
|
+
return;
|
|
19633
|
+
}
|
|
19634
|
+
|
|
19635
|
+
activeId = clickedId;
|
|
19636
|
+
highlight(clickedId);
|
|
19637
|
+
});
|
|
19638
|
+
|
|
19639
|
+
requestAnimationFrame(function() { setTimeout(drawLines, 100); });
|
|
19640
|
+
window.addEventListener('resize', drawLines);
|
|
19641
|
+
})();
|
|
19642
|
+
</script>`;
|
|
18974
19643
|
}
|
|
18975
19644
|
function buildStatusPie(title, counts) {
|
|
18976
19645
|
const entries = Object.entries(counts).filter(([, v]) => v > 0);
|
|
@@ -19050,8 +19719,7 @@ function overviewPage(data, diagrams, navGroups) {
|
|
|
19050
19719
|
${groupSections}
|
|
19051
19720
|
${ungroupedSection}
|
|
19052
19721
|
|
|
19053
|
-
<div class="section-title">Project Timeline
|
|
19054
|
-
${buildTimelineGantt(diagrams)}
|
|
19722
|
+
<div class="section-title"><a href="/timeline">Project Timeline →</a></div>
|
|
19055
19723
|
|
|
19056
19724
|
<div class="section-title">Artifact Relationships</div>
|
|
19057
19725
|
${buildArtifactFlowchart(diagrams)}
|
|
@@ -19306,6 +19974,7 @@ function boardPage(data) {
|
|
|
19306
19974
|
<span>${escapeHtml(col.status)}</span>
|
|
19307
19975
|
<span class="count">${col.docs.length}</span>
|
|
19308
19976
|
</div>
|
|
19977
|
+
<div class="board-column-cards">
|
|
19309
19978
|
${col.docs.map(
|
|
19310
19979
|
(doc) => `
|
|
19311
19980
|
<div class="board-card">
|
|
@@ -19316,6 +19985,7 @@ function boardPage(data) {
|
|
|
19316
19985
|
</a>
|
|
19317
19986
|
</div>`
|
|
19318
19987
|
).join("\n")}
|
|
19988
|
+
</div>
|
|
19319
19989
|
</div>`
|
|
19320
19990
|
).join("\n");
|
|
19321
19991
|
return `
|
|
@@ -19341,6 +20011,18 @@ function boardPage(data) {
|
|
|
19341
20011
|
`;
|
|
19342
20012
|
}
|
|
19343
20013
|
|
|
20014
|
+
// src/web/templates/pages/timeline.ts
|
|
20015
|
+
function timelinePage(diagrams) {
|
|
20016
|
+
return `
|
|
20017
|
+
<div class="page-header">
|
|
20018
|
+
<h2>Project Timeline</h2>
|
|
20019
|
+
<div class="subtitle">Sprint schedule with linked epics</div>
|
|
20020
|
+
</div>
|
|
20021
|
+
|
|
20022
|
+
${buildTimelineGantt(diagrams)}
|
|
20023
|
+
`;
|
|
20024
|
+
}
|
|
20025
|
+
|
|
19344
20026
|
// src/web/router.ts
|
|
19345
20027
|
function handleRequest(req, res, store, projectName, navGroups) {
|
|
19346
20028
|
const parsed = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
@@ -19362,6 +20044,12 @@ function handleRequest(req, res, store, projectName, navGroups) {
|
|
|
19362
20044
|
respond(res, layout({ title: "Overview", activePath: "/", projectName, navGroups }, body));
|
|
19363
20045
|
return;
|
|
19364
20046
|
}
|
|
20047
|
+
if (pathname === "/timeline") {
|
|
20048
|
+
const diagrams = getDiagramData(store);
|
|
20049
|
+
const body = timelinePage(diagrams);
|
|
20050
|
+
respond(res, layout({ title: "Timeline", activePath: "/timeline", projectName, navGroups, mainClass: "expanded" }, body));
|
|
20051
|
+
return;
|
|
20052
|
+
}
|
|
19365
20053
|
if (pathname === "/gar") {
|
|
19366
20054
|
const report = getGarData(store, projectName);
|
|
19367
20055
|
const body = garPage(report);
|
|
@@ -19435,8 +20123,8 @@ import * as http from "http";
|
|
|
19435
20123
|
import { exec } from "child_process";
|
|
19436
20124
|
|
|
19437
20125
|
// src/skills/registry.ts
|
|
19438
|
-
import * as
|
|
19439
|
-
import * as
|
|
20126
|
+
import * as fs9 from "fs";
|
|
20127
|
+
import * as path9 from "path";
|
|
19440
20128
|
import { fileURLToPath } from "url";
|
|
19441
20129
|
import * as YAML5 from "yaml";
|
|
19442
20130
|
import matter2 from "gray-matter";
|
|
@@ -19479,7 +20167,7 @@ Be thorough but concise. Focus on actionable insights.`,
|
|
|
19479
20167
|
};
|
|
19480
20168
|
|
|
19481
20169
|
// src/skills/builtin/jira/tools.ts
|
|
19482
|
-
import { tool as
|
|
20170
|
+
import { tool as tool20 } from "@anthropic-ai/claude-agent-sdk";
|
|
19483
20171
|
|
|
19484
20172
|
// src/skills/builtin/jira/client.ts
|
|
19485
20173
|
var JiraClient = class {
|
|
@@ -19489,8 +20177,8 @@ var JiraClient = class {
|
|
|
19489
20177
|
this.baseUrl = `https://${config2.host}/rest/api/2`;
|
|
19490
20178
|
this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
|
|
19491
20179
|
}
|
|
19492
|
-
async request(
|
|
19493
|
-
const url2 = `${this.baseUrl}${
|
|
20180
|
+
async request(path21, method = "GET", body) {
|
|
20181
|
+
const url2 = `${this.baseUrl}${path21}`;
|
|
19494
20182
|
const headers = {
|
|
19495
20183
|
Authorization: this.authHeader,
|
|
19496
20184
|
"Content-Type": "application/json",
|
|
@@ -19504,7 +20192,7 @@ var JiraClient = class {
|
|
|
19504
20192
|
if (!response.ok) {
|
|
19505
20193
|
const text = await response.text().catch(() => "");
|
|
19506
20194
|
throw new Error(
|
|
19507
|
-
`Jira API error ${response.status} ${method} ${
|
|
20195
|
+
`Jira API error ${response.status} ${method} ${path21}: ${text}`
|
|
19508
20196
|
);
|
|
19509
20197
|
}
|
|
19510
20198
|
if (response.status === 204) return void 0;
|
|
@@ -19588,7 +20276,7 @@ function createJiraTools(store) {
|
|
|
19588
20276
|
const jiraUserConfig = loadUserConfig().jira;
|
|
19589
20277
|
return [
|
|
19590
20278
|
// --- Local read tools ---
|
|
19591
|
-
|
|
20279
|
+
tool20(
|
|
19592
20280
|
"list_jira_issues",
|
|
19593
20281
|
"List locally synced Jira issues (JI-xxx documents), optionally filtered by status or Jira key",
|
|
19594
20282
|
{
|
|
@@ -19616,7 +20304,7 @@ function createJiraTools(store) {
|
|
|
19616
20304
|
},
|
|
19617
20305
|
{ annotations: { readOnlyHint: true } }
|
|
19618
20306
|
),
|
|
19619
|
-
|
|
20307
|
+
tool20(
|
|
19620
20308
|
"get_jira_issue",
|
|
19621
20309
|
"Get the full content of a locally synced Jira issue by local ID (JI-xxx) or Jira key (PROJ-123)",
|
|
19622
20310
|
{
|
|
@@ -19649,7 +20337,7 @@ function createJiraTools(store) {
|
|
|
19649
20337
|
{ annotations: { readOnlyHint: true } }
|
|
19650
20338
|
),
|
|
19651
20339
|
// --- Jira → Local tools ---
|
|
19652
|
-
|
|
20340
|
+
tool20(
|
|
19653
20341
|
"pull_jira_issue",
|
|
19654
20342
|
"Fetch a single Jira issue by key and create/update a local JI-xxx document",
|
|
19655
20343
|
{
|
|
@@ -19696,7 +20384,7 @@ function createJiraTools(store) {
|
|
|
19696
20384
|
};
|
|
19697
20385
|
}
|
|
19698
20386
|
),
|
|
19699
|
-
|
|
20387
|
+
tool20(
|
|
19700
20388
|
"pull_jira_issues_jql",
|
|
19701
20389
|
"Bulk fetch Jira issues via JQL query and create/update local JI-xxx documents",
|
|
19702
20390
|
{
|
|
@@ -19744,7 +20432,7 @@ function createJiraTools(store) {
|
|
|
19744
20432
|
}
|
|
19745
20433
|
),
|
|
19746
20434
|
// --- Local → Jira tools ---
|
|
19747
|
-
|
|
20435
|
+
tool20(
|
|
19748
20436
|
"push_artifact_to_jira",
|
|
19749
20437
|
"Create a Jira issue from any Marvin artifact (D/A/Q/F/E) and create a tracking JI-xxx document",
|
|
19750
20438
|
{
|
|
@@ -19805,7 +20493,7 @@ function createJiraTools(store) {
|
|
|
19805
20493
|
}
|
|
19806
20494
|
),
|
|
19807
20495
|
// --- Bidirectional sync ---
|
|
19808
|
-
|
|
20496
|
+
tool20(
|
|
19809
20497
|
"sync_jira_issue",
|
|
19810
20498
|
"Bidirectional sync: push local title/description to Jira, pull latest status/assignee/labels back",
|
|
19811
20499
|
{
|
|
@@ -19846,7 +20534,7 @@ function createJiraTools(store) {
|
|
|
19846
20534
|
}
|
|
19847
20535
|
),
|
|
19848
20536
|
// --- Local link tool ---
|
|
19849
|
-
|
|
20537
|
+
tool20(
|
|
19850
20538
|
"link_artifact_to_jira",
|
|
19851
20539
|
"Add a Marvin artifact ID to a JI-xxx document's linkedArtifacts field",
|
|
19852
20540
|
{
|
|
@@ -19934,13 +20622,13 @@ var jiraSkill = {
|
|
|
19934
20622
|
**Available tools:**
|
|
19935
20623
|
- \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues
|
|
19936
20624
|
- \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
|
|
19937
|
-
- \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, action, epic, etc.)
|
|
20625
|
+
- \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, action, epic, task, etc.)
|
|
19938
20626
|
- \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
|
|
19939
20627
|
- \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx
|
|
19940
20628
|
|
|
19941
20629
|
**As Tech Lead, use Jira integration to:**
|
|
19942
20630
|
- Pull technical issues and bugs for sprint planning and estimation
|
|
19943
|
-
- Push epics and technical decisions to Jira for cross-team visibility
|
|
20631
|
+
- Push epics, tasks, and technical decisions to Jira for cross-team visibility
|
|
19944
20632
|
- Bidirectional sync to keep local governance and Jira in alignment
|
|
19945
20633
|
- Use JQL queries to track technical debt (e.g. \`labels = "tech-debt" AND status != "Done"\`)`,
|
|
19946
20634
|
"delivery-manager": `You have the **Jira Integration** skill. You can pull issues from Jira and push Marvin artifacts to Jira.
|
|
@@ -19954,16 +20642,420 @@ var jiraSkill = {
|
|
|
19954
20642
|
|
|
19955
20643
|
**As Delivery Manager, use Jira integration to:**
|
|
19956
20644
|
- Pull sprint issues for tracking progress and blockers
|
|
19957
|
-
- Push actions and
|
|
20645
|
+
- Push actions, decisions, and tasks to Jira for stakeholder visibility
|
|
19958
20646
|
- Use JQL queries for reporting (e.g. \`sprint in openSprints() AND assignee = currentUser()\`)
|
|
19959
20647
|
- Sync status between Marvin governance items and Jira issues`
|
|
19960
20648
|
}
|
|
19961
20649
|
};
|
|
19962
20650
|
|
|
20651
|
+
// src/skills/builtin/prd-generator/tools.ts
|
|
20652
|
+
import * as fs8 from "fs";
|
|
20653
|
+
import * as path8 from "path";
|
|
20654
|
+
import { tool as tool21 } from "@anthropic-ai/claude-agent-sdk";
|
|
20655
|
+
var PRIORITY_ORDER2 = {
|
|
20656
|
+
critical: 0,
|
|
20657
|
+
high: 1,
|
|
20658
|
+
medium: 2,
|
|
20659
|
+
low: 3
|
|
20660
|
+
};
|
|
20661
|
+
function priorityRank2(p) {
|
|
20662
|
+
return PRIORITY_ORDER2[p ?? ""] ?? 99;
|
|
20663
|
+
}
|
|
20664
|
+
function gatherContext(store, focusFeature, includeDecisions = true, includeQuestions = true) {
|
|
20665
|
+
const allFeatures = store.list({ type: "feature" });
|
|
20666
|
+
const allEpics = store.list({ type: "epic" });
|
|
20667
|
+
const allTasks = store.list({ type: "task" });
|
|
20668
|
+
const allDecisions = includeDecisions ? store.list({ type: "decision" }) : [];
|
|
20669
|
+
const allQuestions = includeQuestions ? store.list({ type: "question" }) : [];
|
|
20670
|
+
const allActions = store.list({ type: "action" });
|
|
20671
|
+
let features = allFeatures;
|
|
20672
|
+
let epics = allEpics;
|
|
20673
|
+
let tasks = allTasks;
|
|
20674
|
+
if (focusFeature) {
|
|
20675
|
+
features = features.filter((f) => f.frontmatter.id === focusFeature);
|
|
20676
|
+
const featureIds = new Set(features.map((f) => f.frontmatter.id));
|
|
20677
|
+
epics = epics.filter(
|
|
20678
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).some((id) => featureIds.has(id))
|
|
20679
|
+
);
|
|
20680
|
+
const epicIds2 = new Set(epics.map((e) => e.frontmatter.id));
|
|
20681
|
+
tasks = tasks.filter(
|
|
20682
|
+
(t) => normalizeLinkedEpics(t.frontmatter.linkedEpic).some((id) => epicIds2.has(id))
|
|
20683
|
+
);
|
|
20684
|
+
}
|
|
20685
|
+
const featuresByStatus = {};
|
|
20686
|
+
for (const f of features) {
|
|
20687
|
+
featuresByStatus[f.frontmatter.status] = (featuresByStatus[f.frontmatter.status] ?? 0) + 1;
|
|
20688
|
+
}
|
|
20689
|
+
const epicsByStatus = {};
|
|
20690
|
+
for (const e of epics) {
|
|
20691
|
+
epicsByStatus[e.frontmatter.status] = (epicsByStatus[e.frontmatter.status] ?? 0) + 1;
|
|
20692
|
+
}
|
|
20693
|
+
const epicIds = new Set(epics.map((e) => e.frontmatter.id));
|
|
20694
|
+
return {
|
|
20695
|
+
features: features.sort((a, b) => priorityRank2(a.frontmatter.priority) - priorityRank2(b.frontmatter.priority)).map((f) => ({
|
|
20696
|
+
id: f.frontmatter.id,
|
|
20697
|
+
title: f.frontmatter.title,
|
|
20698
|
+
status: f.frontmatter.status,
|
|
20699
|
+
priority: f.frontmatter.priority ?? "medium",
|
|
20700
|
+
content: f.content,
|
|
20701
|
+
linkedEpicCount: epics.filter(
|
|
20702
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).includes(f.frontmatter.id)
|
|
20703
|
+
).length
|
|
20704
|
+
})),
|
|
20705
|
+
epics: epics.map((e) => ({
|
|
20706
|
+
id: e.frontmatter.id,
|
|
20707
|
+
title: e.frontmatter.title,
|
|
20708
|
+
status: e.frontmatter.status,
|
|
20709
|
+
linkedFeature: normalizeLinkedFeatures(e.frontmatter.linkedFeature),
|
|
20710
|
+
targetDate: e.frontmatter.targetDate ?? null,
|
|
20711
|
+
estimatedEffort: e.frontmatter.estimatedEffort ?? null,
|
|
20712
|
+
content: e.content,
|
|
20713
|
+
linkedTaskCount: tasks.filter(
|
|
20714
|
+
(t) => normalizeLinkedEpics(t.frontmatter.linkedEpic).includes(e.frontmatter.id)
|
|
20715
|
+
).length
|
|
20716
|
+
})),
|
|
20717
|
+
tasks: tasks.map((t) => ({
|
|
20718
|
+
id: t.frontmatter.id,
|
|
20719
|
+
title: t.frontmatter.title,
|
|
20720
|
+
status: t.frontmatter.status,
|
|
20721
|
+
linkedEpic: normalizeLinkedEpics(t.frontmatter.linkedEpic),
|
|
20722
|
+
acceptanceCriteria: t.frontmatter.acceptanceCriteria ?? null,
|
|
20723
|
+
technicalNotes: t.frontmatter.technicalNotes ?? null,
|
|
20724
|
+
complexity: t.frontmatter.complexity ?? null,
|
|
20725
|
+
estimatedPoints: t.frontmatter.estimatedPoints ?? null,
|
|
20726
|
+
priority: t.frontmatter.priority ?? null
|
|
20727
|
+
})),
|
|
20728
|
+
decisions: allDecisions.map((d) => ({
|
|
20729
|
+
id: d.frontmatter.id,
|
|
20730
|
+
title: d.frontmatter.title,
|
|
20731
|
+
status: d.frontmatter.status,
|
|
20732
|
+
content: d.content
|
|
20733
|
+
})),
|
|
20734
|
+
questions: allQuestions.map((q) => ({
|
|
20735
|
+
id: q.frontmatter.id,
|
|
20736
|
+
title: q.frontmatter.title,
|
|
20737
|
+
status: q.frontmatter.status,
|
|
20738
|
+
content: q.content
|
|
20739
|
+
})),
|
|
20740
|
+
actions: allActions.filter((a) => {
|
|
20741
|
+
if (!focusFeature) return true;
|
|
20742
|
+
const tags = a.frontmatter.tags ?? [];
|
|
20743
|
+
return tags.some((t) => t.startsWith("epic:") && epicIds.has(t.replace("epic:", "")));
|
|
20744
|
+
}).map((a) => ({
|
|
20745
|
+
id: a.frontmatter.id,
|
|
20746
|
+
title: a.frontmatter.title,
|
|
20747
|
+
status: a.frontmatter.status,
|
|
20748
|
+
owner: a.frontmatter.owner ?? null,
|
|
20749
|
+
priority: a.frontmatter.priority ?? null,
|
|
20750
|
+
dueDate: a.frontmatter.dueDate ?? null
|
|
20751
|
+
})),
|
|
20752
|
+
summary: {
|
|
20753
|
+
totalFeatures: features.length,
|
|
20754
|
+
totalEpics: epics.length,
|
|
20755
|
+
totalTasks: tasks.length,
|
|
20756
|
+
featuresByStatus,
|
|
20757
|
+
epicsByStatus
|
|
20758
|
+
}
|
|
20759
|
+
};
|
|
20760
|
+
}
|
|
20761
|
+
function generateTaskMasterPrd(title, ctx, projectOverview) {
|
|
20762
|
+
const lines = [];
|
|
20763
|
+
lines.push(`# ${title}`);
|
|
20764
|
+
lines.push("");
|
|
20765
|
+
lines.push("## Project Overview");
|
|
20766
|
+
if (projectOverview) {
|
|
20767
|
+
lines.push(projectOverview);
|
|
20768
|
+
} else if (ctx.features.length > 0) {
|
|
20769
|
+
lines.push(`This project encompasses ${ctx.features.length} feature(s) spanning ${ctx.epics.length} epic(s) and ${ctx.tasks.length} implementation task(s).`);
|
|
20770
|
+
}
|
|
20771
|
+
lines.push("");
|
|
20772
|
+
lines.push("## Goals");
|
|
20773
|
+
for (const f of ctx.features) {
|
|
20774
|
+
lines.push(`- **${f.title}** (${f.id}, Priority: ${f.priority}) \u2014 ${f.status}`);
|
|
20775
|
+
}
|
|
20776
|
+
lines.push("");
|
|
20777
|
+
lines.push("## Features and Requirements");
|
|
20778
|
+
lines.push("");
|
|
20779
|
+
for (const feature of ctx.features) {
|
|
20780
|
+
lines.push(`### ${feature.title} (${feature.id}) \u2014 Priority: ${feature.priority}`);
|
|
20781
|
+
lines.push("");
|
|
20782
|
+
if (feature.content) {
|
|
20783
|
+
lines.push(feature.content);
|
|
20784
|
+
lines.push("");
|
|
20785
|
+
}
|
|
20786
|
+
const featureEpics = ctx.epics.filter((e) => e.linkedFeature.includes(feature.id));
|
|
20787
|
+
if (featureEpics.length > 0) {
|
|
20788
|
+
lines.push("#### User Stories / Epics");
|
|
20789
|
+
lines.push("");
|
|
20790
|
+
for (const epic of featureEpics) {
|
|
20791
|
+
const effort = epic.estimatedEffort ? `, Effort: ${epic.estimatedEffort}` : "";
|
|
20792
|
+
lines.push(`- **${epic.id}: ${epic.title}** \u2014 Status: ${epic.status}${effort}`);
|
|
20793
|
+
if (epic.content) {
|
|
20794
|
+
lines.push(` ${epic.content.split("\n")[0]}`);
|
|
20795
|
+
}
|
|
20796
|
+
const epicTasks = ctx.tasks.filter((t) => t.linkedEpic.includes(epic.id));
|
|
20797
|
+
if (epicTasks.length > 0) {
|
|
20798
|
+
lines.push("");
|
|
20799
|
+
lines.push("#### Implementation Tasks");
|
|
20800
|
+
lines.push("");
|
|
20801
|
+
for (const task of epicTasks) {
|
|
20802
|
+
const complexity = task.complexity ? `, Complexity: ${task.complexity}` : "";
|
|
20803
|
+
const points = task.estimatedPoints != null ? `, Points: ${task.estimatedPoints}` : "";
|
|
20804
|
+
lines.push(`- **${task.id}: ${task.title}**${complexity}${points}`);
|
|
20805
|
+
if (task.acceptanceCriteria) {
|
|
20806
|
+
lines.push(` Acceptance Criteria: ${task.acceptanceCriteria}`);
|
|
20807
|
+
}
|
|
20808
|
+
}
|
|
20809
|
+
}
|
|
20810
|
+
}
|
|
20811
|
+
lines.push("");
|
|
20812
|
+
}
|
|
20813
|
+
}
|
|
20814
|
+
const approvedDecisions = ctx.decisions.filter((d) => d.status === "approved" || d.status === "accepted");
|
|
20815
|
+
const openQuestions = ctx.questions.filter((q) => q.status === "open");
|
|
20816
|
+
const technicalNotes = ctx.tasks.filter((t) => t.technicalNotes).map((t) => `- **${t.id}**: ${t.technicalNotes}`);
|
|
20817
|
+
if (approvedDecisions.length > 0 || openQuestions.length > 0 || technicalNotes.length > 0) {
|
|
20818
|
+
lines.push("## Technical Considerations");
|
|
20819
|
+
lines.push("");
|
|
20820
|
+
if (approvedDecisions.length > 0) {
|
|
20821
|
+
lines.push("### Key Decisions");
|
|
20822
|
+
for (const d of approvedDecisions) {
|
|
20823
|
+
lines.push(`- **${d.id}: ${d.title}** \u2014 ${d.content.split("\n")[0]}`);
|
|
20824
|
+
}
|
|
20825
|
+
lines.push("");
|
|
20826
|
+
}
|
|
20827
|
+
if (technicalNotes.length > 0) {
|
|
20828
|
+
lines.push("### Technical Notes");
|
|
20829
|
+
for (const note of technicalNotes) {
|
|
20830
|
+
lines.push(note);
|
|
20831
|
+
}
|
|
20832
|
+
lines.push("");
|
|
20833
|
+
}
|
|
20834
|
+
if (openQuestions.length > 0) {
|
|
20835
|
+
lines.push("### Open Questions");
|
|
20836
|
+
for (const q of openQuestions) {
|
|
20837
|
+
lines.push(`- **${q.id}: ${q.title}** \u2014 ${q.content.split("\n")[0]}`);
|
|
20838
|
+
}
|
|
20839
|
+
lines.push("");
|
|
20840
|
+
}
|
|
20841
|
+
}
|
|
20842
|
+
lines.push("## Implementation Priorities");
|
|
20843
|
+
lines.push("");
|
|
20844
|
+
let priorityIdx = 1;
|
|
20845
|
+
for (const feature of ctx.features) {
|
|
20846
|
+
const featureEpics = ctx.epics.filter((e) => e.linkedFeature.includes(feature.id)).sort((a, b) => {
|
|
20847
|
+
const statusOrder = { "in-progress": 0, planned: 1, done: 2 };
|
|
20848
|
+
return (statusOrder[a.status] ?? 99) - (statusOrder[b.status] ?? 99);
|
|
20849
|
+
});
|
|
20850
|
+
if (featureEpics.length === 0) continue;
|
|
20851
|
+
lines.push(`${priorityIdx}. **${feature.title}** (${feature.priority})`);
|
|
20852
|
+
for (const epic of featureEpics) {
|
|
20853
|
+
const epicTasks = ctx.tasks.filter((t) => t.linkedEpic.includes(epic.id));
|
|
20854
|
+
lines.push(` - ${epic.id}: ${epic.title} (${epic.status}) \u2014 ${epicTasks.length} task(s)`);
|
|
20855
|
+
}
|
|
20856
|
+
priorityIdx++;
|
|
20857
|
+
}
|
|
20858
|
+
lines.push("");
|
|
20859
|
+
return lines.join("\n");
|
|
20860
|
+
}
|
|
20861
|
+
function generateClaudeCodePrd(title, ctx, projectOverview) {
|
|
20862
|
+
const lines = [];
|
|
20863
|
+
lines.push(`# ${title}`);
|
|
20864
|
+
lines.push("");
|
|
20865
|
+
lines.push("## Overview");
|
|
20866
|
+
if (projectOverview) {
|
|
20867
|
+
lines.push(projectOverview);
|
|
20868
|
+
} else if (ctx.features.length > 0) {
|
|
20869
|
+
lines.push(`This project encompasses ${ctx.features.length} feature(s) spanning ${ctx.epics.length} epic(s) and ${ctx.tasks.length} implementation task(s).`);
|
|
20870
|
+
}
|
|
20871
|
+
lines.push("");
|
|
20872
|
+
const approvedDecisions = ctx.decisions.filter((d) => d.status === "approved" || d.status === "accepted");
|
|
20873
|
+
if (approvedDecisions.length > 0) {
|
|
20874
|
+
lines.push("## Architecture & Technical Decisions");
|
|
20875
|
+
lines.push("");
|
|
20876
|
+
for (const d of approvedDecisions) {
|
|
20877
|
+
lines.push(`### ${d.id}: ${d.title}`);
|
|
20878
|
+
lines.push(d.content);
|
|
20879
|
+
lines.push("");
|
|
20880
|
+
}
|
|
20881
|
+
}
|
|
20882
|
+
lines.push("## Implementation Plan");
|
|
20883
|
+
lines.push("");
|
|
20884
|
+
const priorityGroups = {};
|
|
20885
|
+
for (const f of ctx.features) {
|
|
20886
|
+
const group = f.priority === "critical" || f.priority === "high" ? "Phase 1: High Priority" : "Phase 2: Medium & Low Priority";
|
|
20887
|
+
if (!priorityGroups[group]) priorityGroups[group] = [];
|
|
20888
|
+
priorityGroups[group].push(f);
|
|
20889
|
+
}
|
|
20890
|
+
for (const [phase, features] of Object.entries(priorityGroups)) {
|
|
20891
|
+
lines.push(`### ${phase}`);
|
|
20892
|
+
lines.push("");
|
|
20893
|
+
for (const feature of features) {
|
|
20894
|
+
const featureEpics = ctx.epics.filter((e) => e.linkedFeature.includes(feature.id));
|
|
20895
|
+
for (const epic of featureEpics) {
|
|
20896
|
+
lines.push(`- [ ] ${epic.id}: ${epic.title}`);
|
|
20897
|
+
const epicTasks = ctx.tasks.filter((t) => t.linkedEpic.includes(epic.id));
|
|
20898
|
+
for (const task of epicTasks) {
|
|
20899
|
+
const complexity = task.complexity ? `complexity: ${task.complexity}` : "";
|
|
20900
|
+
const points = task.estimatedPoints != null ? `points: ${task.estimatedPoints}` : "";
|
|
20901
|
+
const meta3 = [complexity, points].filter(Boolean).join(", ");
|
|
20902
|
+
lines.push(` - [ ] ${task.id}: ${task.title}${meta3 ? ` (${meta3})` : ""}`);
|
|
20903
|
+
if (task.acceptanceCriteria) {
|
|
20904
|
+
lines.push(` - Acceptance: ${task.acceptanceCriteria}`);
|
|
20905
|
+
}
|
|
20906
|
+
if (task.technicalNotes) {
|
|
20907
|
+
lines.push(` - Notes: ${task.technicalNotes}`);
|
|
20908
|
+
}
|
|
20909
|
+
}
|
|
20910
|
+
}
|
|
20911
|
+
}
|
|
20912
|
+
lines.push("");
|
|
20913
|
+
}
|
|
20914
|
+
const openQuestions = ctx.questions.filter((q) => q.status === "open");
|
|
20915
|
+
if (openQuestions.length > 0) {
|
|
20916
|
+
lines.push("## Open Questions");
|
|
20917
|
+
lines.push("");
|
|
20918
|
+
for (const q of openQuestions) {
|
|
20919
|
+
lines.push(`- **${q.id}: ${q.title}** \u2014 ${q.content.split("\n")[0]}`);
|
|
20920
|
+
}
|
|
20921
|
+
lines.push("");
|
|
20922
|
+
}
|
|
20923
|
+
return lines.join("\n");
|
|
20924
|
+
}
|
|
20925
|
+
function createPrdTools(store) {
|
|
20926
|
+
return [
|
|
20927
|
+
tool21(
|
|
20928
|
+
"gather_prd_context",
|
|
20929
|
+
"Aggregate all governance artifacts (features, epics, tasks, decisions, questions, actions) into structured JSON for PRD generation",
|
|
20930
|
+
{
|
|
20931
|
+
focusFeature: external_exports.string().optional().describe("Filter context to a specific feature ID (e.g. 'F-001')"),
|
|
20932
|
+
includeDecisions: external_exports.boolean().optional().describe("Include decisions in context (default: true)"),
|
|
20933
|
+
includeQuestions: external_exports.boolean().optional().describe("Include questions in context (default: true)")
|
|
20934
|
+
},
|
|
20935
|
+
async (args) => {
|
|
20936
|
+
const ctx = gatherContext(store, args.focusFeature, args.includeDecisions ?? true, args.includeQuestions ?? true);
|
|
20937
|
+
return {
|
|
20938
|
+
content: [{ type: "text", text: JSON.stringify(ctx, null, 2) }]
|
|
20939
|
+
};
|
|
20940
|
+
},
|
|
20941
|
+
{ annotations: { readOnlyHint: true } }
|
|
20942
|
+
),
|
|
20943
|
+
tool21(
|
|
20944
|
+
"generate_prd",
|
|
20945
|
+
"Generate a PRD document from governance artifacts and save it as a PRD-xxx document",
|
|
20946
|
+
{
|
|
20947
|
+
title: external_exports.string().describe("PRD title"),
|
|
20948
|
+
format: external_exports.enum(["taskmaster", "claude-code"]).describe("Output format: 'taskmaster' for Claude TaskMaster parse_prd, 'claude-code' for Claude Code consumption"),
|
|
20949
|
+
projectOverview: external_exports.string().optional().describe("Project overview text (synthesized from features if not provided)"),
|
|
20950
|
+
focusFeature: external_exports.string().optional().describe("Focus on a specific feature ID (e.g. 'F-001')"),
|
|
20951
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Tags for the PRD document")
|
|
20952
|
+
},
|
|
20953
|
+
async (args) => {
|
|
20954
|
+
const ctx = gatherContext(store, args.focusFeature);
|
|
20955
|
+
const prdContent = args.format === "taskmaster" ? generateTaskMasterPrd(args.title, ctx, args.projectOverview) : generateClaudeCodePrd(args.title, ctx, args.projectOverview);
|
|
20956
|
+
const frontmatter = {
|
|
20957
|
+
title: args.title,
|
|
20958
|
+
status: "draft",
|
|
20959
|
+
format: args.format
|
|
20960
|
+
};
|
|
20961
|
+
if (args.focusFeature) frontmatter.focusFeature = args.focusFeature;
|
|
20962
|
+
if (args.tags) frontmatter.tags = args.tags;
|
|
20963
|
+
const doc = store.create("prd", frontmatter, prdContent);
|
|
20964
|
+
return {
|
|
20965
|
+
content: [
|
|
20966
|
+
{
|
|
20967
|
+
type: "text",
|
|
20968
|
+
text: `Generated PRD ${doc.frontmatter.id}: "${args.title}" (format: ${args.format}, ${ctx.summary.totalFeatures} features, ${ctx.summary.totalEpics} epics, ${ctx.summary.totalTasks} tasks)`
|
|
20969
|
+
}
|
|
20970
|
+
]
|
|
20971
|
+
};
|
|
20972
|
+
}
|
|
20973
|
+
),
|
|
20974
|
+
tool21(
|
|
20975
|
+
"export_prd",
|
|
20976
|
+
"Export a PRD document to a file path for external consumption (e.g. by Claude TaskMaster or Claude Code)",
|
|
20977
|
+
{
|
|
20978
|
+
prdId: external_exports.string().describe("PRD document ID (e.g. 'PRD-001')"),
|
|
20979
|
+
outputPath: external_exports.string().describe("File path to write the PRD content to")
|
|
20980
|
+
},
|
|
20981
|
+
async (args) => {
|
|
20982
|
+
const doc = store.get(args.prdId);
|
|
20983
|
+
if (!doc) {
|
|
20984
|
+
return {
|
|
20985
|
+
content: [{ type: "text", text: `PRD ${args.prdId} not found` }],
|
|
20986
|
+
isError: true
|
|
20987
|
+
};
|
|
20988
|
+
}
|
|
20989
|
+
const outputDir = path8.dirname(args.outputPath);
|
|
20990
|
+
fs8.mkdirSync(outputDir, { recursive: true });
|
|
20991
|
+
fs8.writeFileSync(args.outputPath, doc.content, "utf-8");
|
|
20992
|
+
return {
|
|
20993
|
+
content: [
|
|
20994
|
+
{
|
|
20995
|
+
type: "text",
|
|
20996
|
+
text: `Exported PRD ${args.prdId} to ${args.outputPath}`
|
|
20997
|
+
}
|
|
20998
|
+
]
|
|
20999
|
+
};
|
|
21000
|
+
}
|
|
21001
|
+
)
|
|
21002
|
+
];
|
|
21003
|
+
}
|
|
21004
|
+
|
|
21005
|
+
// src/skills/builtin/prd-generator/index.ts
|
|
21006
|
+
var prdGeneratorSkill = {
|
|
21007
|
+
id: "prd-generator",
|
|
21008
|
+
name: "PRD Generator",
|
|
21009
|
+
description: "Generate PRDs from governance artifacts for TaskMaster or Claude Code",
|
|
21010
|
+
version: "1.0.0",
|
|
21011
|
+
format: "builtin-ts",
|
|
21012
|
+
documentTypeRegistrations: [
|
|
21013
|
+
{ type: "prd", dirName: "prds", idPrefix: "PRD" }
|
|
21014
|
+
],
|
|
21015
|
+
tools: (store) => createPrdTools(store),
|
|
21016
|
+
promptFragments: {
|
|
21017
|
+
"tech-lead": `You have the **PRD Generator** skill. You can generate Product Requirements Documents from governance artifacts.
|
|
21018
|
+
|
|
21019
|
+
**Available tools:**
|
|
21020
|
+
- \`gather_prd_context\` \u2014 aggregate features, epics, tasks, decisions, questions, and actions into structured JSON for analysis
|
|
21021
|
+
- \`generate_prd\` \u2014 generate a formatted PRD document and save it as PRD-xxx. Supports "taskmaster" format (for Claude TaskMaster parse_prd) and "claude-code" format (for Claude Code consumption)
|
|
21022
|
+
- \`export_prd\` \u2014 export a PRD document to a file path for external use
|
|
21023
|
+
|
|
21024
|
+
**As Tech Lead, use PRD generation to:**
|
|
21025
|
+
- Create comprehensive PRDs that capture the full governance context
|
|
21026
|
+
- Export TaskMaster-format PRDs for automated task breakdown via \`parse_prd\`
|
|
21027
|
+
- Export Claude Code-format PRDs as implementation plans with checklists
|
|
21028
|
+
- Focus PRDs on specific features using the focusFeature parameter`,
|
|
21029
|
+
"delivery-manager": `You have the **PRD Generator** skill. You can generate Product Requirements Documents from governance artifacts.
|
|
21030
|
+
|
|
21031
|
+
**Available tools:**
|
|
21032
|
+
- \`gather_prd_context\` \u2014 aggregate all governance artifacts into structured JSON for review
|
|
21033
|
+
- \`generate_prd\` \u2014 generate a formatted PRD document (taskmaster or claude-code format)
|
|
21034
|
+
- \`export_prd\` \u2014 export a PRD to a file path
|
|
21035
|
+
|
|
21036
|
+
**As Delivery Manager, use PRD generation to:**
|
|
21037
|
+
- Generate PRDs for stakeholder communication and project documentation
|
|
21038
|
+
- Review aggregated project context before sprint planning
|
|
21039
|
+
- Export PRDs to share with external teams or tools`,
|
|
21040
|
+
"product-owner": `You have the **PRD Generator** skill. You can generate Product Requirements Documents from governance artifacts.
|
|
21041
|
+
|
|
21042
|
+
**Available tools:**
|
|
21043
|
+
- \`gather_prd_context\` \u2014 aggregate features, epics, tasks, and decisions into structured JSON
|
|
21044
|
+
- \`generate_prd\` \u2014 generate a formatted PRD document
|
|
21045
|
+
- \`export_prd\` \u2014 export a PRD to a file path
|
|
21046
|
+
|
|
21047
|
+
**As Product Owner, use PRD generation to:**
|
|
21048
|
+
- Generate PRDs that capture feature requirements and priorities
|
|
21049
|
+
- Review the complete governance context for product planning
|
|
21050
|
+
- Export PRDs for stakeholder review and sign-off`
|
|
21051
|
+
}
|
|
21052
|
+
};
|
|
21053
|
+
|
|
19963
21054
|
// src/skills/registry.ts
|
|
19964
21055
|
var BUILTIN_SKILLS = {
|
|
19965
21056
|
"governance-review": governanceReviewSkill,
|
|
19966
|
-
"jira": jiraSkill
|
|
21057
|
+
"jira": jiraSkill,
|
|
21058
|
+
"prd-generator": prdGeneratorSkill
|
|
19967
21059
|
};
|
|
19968
21060
|
var GOVERNANCE_TOOL_NAMES = [
|
|
19969
21061
|
"mcp__marvin-governance__list_decisions",
|
|
@@ -19984,13 +21076,13 @@ var GOVERNANCE_TOOL_NAMES = [
|
|
|
19984
21076
|
];
|
|
19985
21077
|
function getBuiltinSkillsDir() {
|
|
19986
21078
|
const thisFile = fileURLToPath(import.meta.url);
|
|
19987
|
-
return
|
|
21079
|
+
return path9.join(path9.dirname(thisFile), "builtin");
|
|
19988
21080
|
}
|
|
19989
21081
|
function loadSkillFromDirectory(dirPath) {
|
|
19990
|
-
const skillMdPath =
|
|
19991
|
-
if (!
|
|
21082
|
+
const skillMdPath = path9.join(dirPath, "SKILL.md");
|
|
21083
|
+
if (!fs9.existsSync(skillMdPath)) return void 0;
|
|
19992
21084
|
try {
|
|
19993
|
-
const raw =
|
|
21085
|
+
const raw = fs9.readFileSync(skillMdPath, "utf-8");
|
|
19994
21086
|
const { data, content } = matter2(raw);
|
|
19995
21087
|
if (!data.name || !data.description) return void 0;
|
|
19996
21088
|
const metadata = data.metadata ?? {};
|
|
@@ -20001,13 +21093,13 @@ function loadSkillFromDirectory(dirPath) {
|
|
|
20001
21093
|
if (wildcardPrompt) {
|
|
20002
21094
|
promptFragments["*"] = wildcardPrompt;
|
|
20003
21095
|
}
|
|
20004
|
-
const personasDir =
|
|
20005
|
-
if (
|
|
21096
|
+
const personasDir = path9.join(dirPath, "personas");
|
|
21097
|
+
if (fs9.existsSync(personasDir)) {
|
|
20006
21098
|
try {
|
|
20007
|
-
for (const file2 of
|
|
21099
|
+
for (const file2 of fs9.readdirSync(personasDir)) {
|
|
20008
21100
|
if (!file2.endsWith(".md")) continue;
|
|
20009
21101
|
const personaId = file2.replace(/\.md$/, "");
|
|
20010
|
-
const personaPrompt =
|
|
21102
|
+
const personaPrompt = fs9.readFileSync(path9.join(personasDir, file2), "utf-8").trim();
|
|
20011
21103
|
if (personaPrompt) {
|
|
20012
21104
|
promptFragments[personaId] = personaPrompt;
|
|
20013
21105
|
}
|
|
@@ -20016,10 +21108,10 @@ function loadSkillFromDirectory(dirPath) {
|
|
|
20016
21108
|
}
|
|
20017
21109
|
}
|
|
20018
21110
|
let actions;
|
|
20019
|
-
const actionsPath =
|
|
20020
|
-
if (
|
|
21111
|
+
const actionsPath = path9.join(dirPath, "actions.yaml");
|
|
21112
|
+
if (fs9.existsSync(actionsPath)) {
|
|
20021
21113
|
try {
|
|
20022
|
-
const actionsRaw =
|
|
21114
|
+
const actionsRaw = fs9.readFileSync(actionsPath, "utf-8");
|
|
20023
21115
|
actions = YAML5.parse(actionsRaw);
|
|
20024
21116
|
} catch {
|
|
20025
21117
|
}
|
|
@@ -20046,10 +21138,10 @@ function loadAllSkills(marvinDir) {
|
|
|
20046
21138
|
}
|
|
20047
21139
|
try {
|
|
20048
21140
|
const builtinDir = getBuiltinSkillsDir();
|
|
20049
|
-
if (
|
|
20050
|
-
for (const entry of
|
|
20051
|
-
const entryPath =
|
|
20052
|
-
if (!
|
|
21141
|
+
if (fs9.existsSync(builtinDir)) {
|
|
21142
|
+
for (const entry of fs9.readdirSync(builtinDir)) {
|
|
21143
|
+
const entryPath = path9.join(builtinDir, entry);
|
|
21144
|
+
if (!fs9.statSync(entryPath).isDirectory()) continue;
|
|
20053
21145
|
if (skills.has(entry)) continue;
|
|
20054
21146
|
const skill = loadSkillFromDirectory(entryPath);
|
|
20055
21147
|
if (skill) skills.set(skill.id, skill);
|
|
@@ -20058,18 +21150,18 @@ function loadAllSkills(marvinDir) {
|
|
|
20058
21150
|
} catch {
|
|
20059
21151
|
}
|
|
20060
21152
|
if (marvinDir) {
|
|
20061
|
-
const skillsDir =
|
|
20062
|
-
if (
|
|
21153
|
+
const skillsDir = path9.join(marvinDir, "skills");
|
|
21154
|
+
if (fs9.existsSync(skillsDir)) {
|
|
20063
21155
|
let entries;
|
|
20064
21156
|
try {
|
|
20065
|
-
entries =
|
|
21157
|
+
entries = fs9.readdirSync(skillsDir);
|
|
20066
21158
|
} catch {
|
|
20067
21159
|
entries = [];
|
|
20068
21160
|
}
|
|
20069
21161
|
for (const entry of entries) {
|
|
20070
|
-
const entryPath =
|
|
21162
|
+
const entryPath = path9.join(skillsDir, entry);
|
|
20071
21163
|
try {
|
|
20072
|
-
if (
|
|
21164
|
+
if (fs9.statSync(entryPath).isDirectory()) {
|
|
20073
21165
|
const skill = loadSkillFromDirectory(entryPath);
|
|
20074
21166
|
if (skill) skills.set(skill.id, skill);
|
|
20075
21167
|
continue;
|
|
@@ -20079,7 +21171,7 @@ function loadAllSkills(marvinDir) {
|
|
|
20079
21171
|
}
|
|
20080
21172
|
if (!entry.endsWith(".yaml") && !entry.endsWith(".yml")) continue;
|
|
20081
21173
|
try {
|
|
20082
|
-
const raw =
|
|
21174
|
+
const raw = fs9.readFileSync(entryPath, "utf-8");
|
|
20083
21175
|
const parsed = YAML5.parse(raw);
|
|
20084
21176
|
if (!parsed?.id || !parsed?.name || !parsed?.version) continue;
|
|
20085
21177
|
const skill = {
|
|
@@ -20184,12 +21276,12 @@ function getSkillAgentDefinitions(skillIds, allSkills) {
|
|
|
20184
21276
|
return agents;
|
|
20185
21277
|
}
|
|
20186
21278
|
function migrateYamlToSkillMd(yamlPath, outputDir) {
|
|
20187
|
-
const raw =
|
|
21279
|
+
const raw = fs9.readFileSync(yamlPath, "utf-8");
|
|
20188
21280
|
const parsed = YAML5.parse(raw);
|
|
20189
21281
|
if (!parsed?.id || !parsed?.name) {
|
|
20190
21282
|
throw new Error(`Invalid skill YAML: missing required fields (id, name)`);
|
|
20191
21283
|
}
|
|
20192
|
-
|
|
21284
|
+
fs9.mkdirSync(outputDir, { recursive: true });
|
|
20193
21285
|
const frontmatter = {
|
|
20194
21286
|
name: parsed.id,
|
|
20195
21287
|
description: parsed.description ?? ""
|
|
@@ -20203,15 +21295,15 @@ function migrateYamlToSkillMd(yamlPath, outputDir) {
|
|
|
20203
21295
|
const skillMd = matter2.stringify(wildcardPrompt ? `
|
|
20204
21296
|
${wildcardPrompt}
|
|
20205
21297
|
` : "\n", frontmatter);
|
|
20206
|
-
|
|
21298
|
+
fs9.writeFileSync(path9.join(outputDir, "SKILL.md"), skillMd, "utf-8");
|
|
20207
21299
|
if (promptFragments) {
|
|
20208
21300
|
const personaKeys = Object.keys(promptFragments).filter((k) => k !== "*");
|
|
20209
21301
|
if (personaKeys.length > 0) {
|
|
20210
|
-
const personasDir =
|
|
20211
|
-
|
|
21302
|
+
const personasDir = path9.join(outputDir, "personas");
|
|
21303
|
+
fs9.mkdirSync(personasDir, { recursive: true });
|
|
20212
21304
|
for (const personaId of personaKeys) {
|
|
20213
|
-
|
|
20214
|
-
|
|
21305
|
+
fs9.writeFileSync(
|
|
21306
|
+
path9.join(personasDir, `${personaId}.md`),
|
|
20215
21307
|
`${promptFragments[personaId]}
|
|
20216
21308
|
`,
|
|
20217
21309
|
"utf-8"
|
|
@@ -20221,8 +21313,8 @@ ${wildcardPrompt}
|
|
|
20221
21313
|
}
|
|
20222
21314
|
const actions = parsed.actions;
|
|
20223
21315
|
if (actions && actions.length > 0) {
|
|
20224
|
-
|
|
20225
|
-
|
|
21316
|
+
fs9.writeFileSync(
|
|
21317
|
+
path9.join(outputDir, "actions.yaml"),
|
|
20226
21318
|
YAML5.stringify(actions),
|
|
20227
21319
|
"utf-8"
|
|
20228
21320
|
);
|
|
@@ -20301,7 +21393,7 @@ function openBrowser(url2) {
|
|
|
20301
21393
|
var runningServer = null;
|
|
20302
21394
|
function createWebTools(store, projectName, navGroups) {
|
|
20303
21395
|
return [
|
|
20304
|
-
|
|
21396
|
+
tool22(
|
|
20305
21397
|
"start_web_dashboard",
|
|
20306
21398
|
"Start the Marvin web dashboard on a local port. Returns the base URL. If already running, returns the existing URL.",
|
|
20307
21399
|
{
|
|
@@ -20333,7 +21425,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20333
21425
|
};
|
|
20334
21426
|
}
|
|
20335
21427
|
),
|
|
20336
|
-
|
|
21428
|
+
tool22(
|
|
20337
21429
|
"stop_web_dashboard",
|
|
20338
21430
|
"Stop the running Marvin web dashboard.",
|
|
20339
21431
|
{},
|
|
@@ -20353,7 +21445,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20353
21445
|
};
|
|
20354
21446
|
}
|
|
20355
21447
|
),
|
|
20356
|
-
|
|
21448
|
+
tool22(
|
|
20357
21449
|
"get_web_dashboard_urls",
|
|
20358
21450
|
"Get all available dashboard page URLs. The dashboard must be running.",
|
|
20359
21451
|
{},
|
|
@@ -20379,7 +21471,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20379
21471
|
},
|
|
20380
21472
|
{ annotations: { readOnlyHint: true } }
|
|
20381
21473
|
),
|
|
20382
|
-
|
|
21474
|
+
tool22(
|
|
20383
21475
|
"get_dashboard_overview",
|
|
20384
21476
|
"Get the project overview data: document type counts and recent activity. Works without the web server running.",
|
|
20385
21477
|
{},
|
|
@@ -20401,7 +21493,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20401
21493
|
},
|
|
20402
21494
|
{ annotations: { readOnlyHint: true } }
|
|
20403
21495
|
),
|
|
20404
|
-
|
|
21496
|
+
tool22(
|
|
20405
21497
|
"get_dashboard_gar",
|
|
20406
21498
|
"Get the GAR (Governance, Actions, Risks) report as JSON. Works without the web server running.",
|
|
20407
21499
|
{},
|
|
@@ -20413,7 +21505,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20413
21505
|
},
|
|
20414
21506
|
{ annotations: { readOnlyHint: true } }
|
|
20415
21507
|
),
|
|
20416
|
-
|
|
21508
|
+
tool22(
|
|
20417
21509
|
"get_dashboard_board",
|
|
20418
21510
|
"Get board data showing documents grouped by status. Optionally filter by document type. Works without the web server running.",
|
|
20419
21511
|
{
|
|
@@ -20496,8 +21588,8 @@ function slugify3(text) {
|
|
|
20496
21588
|
}
|
|
20497
21589
|
|
|
20498
21590
|
// src/sources/manifest.ts
|
|
20499
|
-
import * as
|
|
20500
|
-
import * as
|
|
21591
|
+
import * as fs10 from "fs";
|
|
21592
|
+
import * as path10 from "path";
|
|
20501
21593
|
import * as crypto from "crypto";
|
|
20502
21594
|
import * as YAML6 from "yaml";
|
|
20503
21595
|
var MANIFEST_FILE = ".manifest.yaml";
|
|
@@ -20510,37 +21602,37 @@ var SourceManifestManager = class {
|
|
|
20510
21602
|
manifestPath;
|
|
20511
21603
|
sourcesDir;
|
|
20512
21604
|
constructor(marvinDir) {
|
|
20513
|
-
this.sourcesDir =
|
|
20514
|
-
this.manifestPath =
|
|
21605
|
+
this.sourcesDir = path10.join(marvinDir, "sources");
|
|
21606
|
+
this.manifestPath = path10.join(this.sourcesDir, MANIFEST_FILE);
|
|
20515
21607
|
this.manifest = this.load();
|
|
20516
21608
|
}
|
|
20517
21609
|
load() {
|
|
20518
|
-
if (!
|
|
21610
|
+
if (!fs10.existsSync(this.manifestPath)) {
|
|
20519
21611
|
return emptyManifest();
|
|
20520
21612
|
}
|
|
20521
|
-
const raw =
|
|
21613
|
+
const raw = fs10.readFileSync(this.manifestPath, "utf-8");
|
|
20522
21614
|
const parsed = YAML6.parse(raw);
|
|
20523
21615
|
return parsed ?? emptyManifest();
|
|
20524
21616
|
}
|
|
20525
21617
|
save() {
|
|
20526
|
-
|
|
20527
|
-
|
|
21618
|
+
fs10.mkdirSync(this.sourcesDir, { recursive: true });
|
|
21619
|
+
fs10.writeFileSync(this.manifestPath, YAML6.stringify(this.manifest), "utf-8");
|
|
20528
21620
|
}
|
|
20529
21621
|
scan() {
|
|
20530
21622
|
const added = [];
|
|
20531
21623
|
const changed = [];
|
|
20532
21624
|
const removed = [];
|
|
20533
|
-
if (!
|
|
21625
|
+
if (!fs10.existsSync(this.sourcesDir)) {
|
|
20534
21626
|
return { added, changed, removed };
|
|
20535
21627
|
}
|
|
20536
21628
|
const onDisk = new Set(
|
|
20537
|
-
|
|
20538
|
-
const ext =
|
|
21629
|
+
fs10.readdirSync(this.sourcesDir).filter((f) => {
|
|
21630
|
+
const ext = path10.extname(f).toLowerCase();
|
|
20539
21631
|
return SOURCE_EXTENSIONS.includes(ext);
|
|
20540
21632
|
})
|
|
20541
21633
|
);
|
|
20542
21634
|
for (const fileName of onDisk) {
|
|
20543
|
-
const filePath =
|
|
21635
|
+
const filePath = path10.join(this.sourcesDir, fileName);
|
|
20544
21636
|
const hash2 = this.hashFile(filePath);
|
|
20545
21637
|
const existing = this.manifest.files[fileName];
|
|
20546
21638
|
if (!existing) {
|
|
@@ -20603,7 +21695,7 @@ var SourceManifestManager = class {
|
|
|
20603
21695
|
this.save();
|
|
20604
21696
|
}
|
|
20605
21697
|
hashFile(filePath) {
|
|
20606
|
-
const content =
|
|
21698
|
+
const content = fs10.readFileSync(filePath);
|
|
20607
21699
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
20608
21700
|
}
|
|
20609
21701
|
};
|
|
@@ -20618,8 +21710,8 @@ async function startSession(options) {
|
|
|
20618
21710
|
const skillRegistrations = collectSkillRegistrations(skillIds, allSkills);
|
|
20619
21711
|
const store = new DocumentStore(marvinDir, [...pluginRegistrations, ...skillRegistrations]);
|
|
20620
21712
|
const sessionStore = new SessionStore(marvinDir);
|
|
20621
|
-
const sourcesDir =
|
|
20622
|
-
const hasSourcesDir =
|
|
21713
|
+
const sourcesDir = path11.join(marvinDir, "sources");
|
|
21714
|
+
const hasSourcesDir = fs11.existsSync(sourcesDir);
|
|
20623
21715
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
20624
21716
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
20625
21717
|
const pluginPromptFragment = plugin ? getPluginPromptFragment(plugin, persona.id) : void 0;
|
|
@@ -21095,13 +22187,13 @@ async function setApiKey() {
|
|
|
21095
22187
|
}
|
|
21096
22188
|
|
|
21097
22189
|
// src/cli/commands/ingest.ts
|
|
21098
|
-
import * as
|
|
21099
|
-
import * as
|
|
22190
|
+
import * as fs13 from "fs";
|
|
22191
|
+
import * as path13 from "path";
|
|
21100
22192
|
import chalk8 from "chalk";
|
|
21101
22193
|
|
|
21102
22194
|
// src/sources/ingest.ts
|
|
21103
|
-
import * as
|
|
21104
|
-
import * as
|
|
22195
|
+
import * as fs12 from "fs";
|
|
22196
|
+
import * as path12 from "path";
|
|
21105
22197
|
import chalk7 from "chalk";
|
|
21106
22198
|
import ora2 from "ora";
|
|
21107
22199
|
import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -21204,15 +22296,15 @@ async function ingestFile(options) {
|
|
|
21204
22296
|
const persona = getPersona(personaId);
|
|
21205
22297
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21206
22298
|
const sourcesDir = manifest.sourcesDir;
|
|
21207
|
-
const filePath =
|
|
21208
|
-
if (!
|
|
22299
|
+
const filePath = path12.join(sourcesDir, fileName);
|
|
22300
|
+
if (!fs12.existsSync(filePath)) {
|
|
21209
22301
|
throw new Error(`Source file not found: ${filePath}`);
|
|
21210
22302
|
}
|
|
21211
|
-
const ext =
|
|
22303
|
+
const ext = path12.extname(fileName).toLowerCase();
|
|
21212
22304
|
const isPdf = ext === ".pdf";
|
|
21213
22305
|
let fileContent = null;
|
|
21214
22306
|
if (!isPdf) {
|
|
21215
|
-
fileContent =
|
|
22307
|
+
fileContent = fs12.readFileSync(filePath, "utf-8");
|
|
21216
22308
|
}
|
|
21217
22309
|
const store = new DocumentStore(marvinDir);
|
|
21218
22310
|
const createdArtifacts = [];
|
|
@@ -21315,9 +22407,9 @@ Ingest ended with error: ${message.subtype}`)
|
|
|
21315
22407
|
async function ingestCommand(file2, options) {
|
|
21316
22408
|
const project = loadProject();
|
|
21317
22409
|
const marvinDir = project.marvinDir;
|
|
21318
|
-
const sourcesDir =
|
|
21319
|
-
if (!
|
|
21320
|
-
|
|
22410
|
+
const sourcesDir = path13.join(marvinDir, "sources");
|
|
22411
|
+
if (!fs13.existsSync(sourcesDir)) {
|
|
22412
|
+
fs13.mkdirSync(sourcesDir, { recursive: true });
|
|
21321
22413
|
}
|
|
21322
22414
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21323
22415
|
manifest.scan();
|
|
@@ -21328,8 +22420,8 @@ async function ingestCommand(file2, options) {
|
|
|
21328
22420
|
return;
|
|
21329
22421
|
}
|
|
21330
22422
|
if (file2) {
|
|
21331
|
-
const filePath =
|
|
21332
|
-
if (!
|
|
22423
|
+
const filePath = path13.join(sourcesDir, file2);
|
|
22424
|
+
if (!fs13.existsSync(filePath)) {
|
|
21333
22425
|
console.log(chalk8.red(`Source file not found: ${file2}`));
|
|
21334
22426
|
console.log(chalk8.dim(`Expected at: ${filePath}`));
|
|
21335
22427
|
console.log(chalk8.dim(`Drop files into .marvin/sources/ and try again.`));
|
|
@@ -21396,7 +22488,7 @@ import ora3 from "ora";
|
|
|
21396
22488
|
import { input as input3 } from "@inquirer/prompts";
|
|
21397
22489
|
|
|
21398
22490
|
// src/git/repository.ts
|
|
21399
|
-
import * as
|
|
22491
|
+
import * as path14 from "path";
|
|
21400
22492
|
import simpleGit from "simple-git";
|
|
21401
22493
|
var MARVIN_GITIGNORE = `node_modules/
|
|
21402
22494
|
.DS_Store
|
|
@@ -21416,7 +22508,7 @@ var DIR_TYPE_LABELS = {
|
|
|
21416
22508
|
function buildCommitMessage(files) {
|
|
21417
22509
|
const counts = /* @__PURE__ */ new Map();
|
|
21418
22510
|
for (const f of files) {
|
|
21419
|
-
const parts2 = f.split(
|
|
22511
|
+
const parts2 = f.split(path14.sep).join("/").split("/");
|
|
21420
22512
|
const docsIdx = parts2.indexOf("docs");
|
|
21421
22513
|
if (docsIdx !== -1 && docsIdx + 1 < parts2.length) {
|
|
21422
22514
|
const dirName = parts2[docsIdx + 1];
|
|
@@ -21456,9 +22548,9 @@ var MarvinGit = class {
|
|
|
21456
22548
|
);
|
|
21457
22549
|
}
|
|
21458
22550
|
await this.git.init();
|
|
21459
|
-
const { writeFileSync:
|
|
21460
|
-
|
|
21461
|
-
|
|
22551
|
+
const { writeFileSync: writeFileSync11 } = await import("fs");
|
|
22552
|
+
writeFileSync11(
|
|
22553
|
+
path14.join(this.marvinDir, ".gitignore"),
|
|
21462
22554
|
MARVIN_GITIGNORE,
|
|
21463
22555
|
"utf-8"
|
|
21464
22556
|
);
|
|
@@ -21578,7 +22670,7 @@ var MarvinGit = class {
|
|
|
21578
22670
|
}
|
|
21579
22671
|
}
|
|
21580
22672
|
static async clone(url2, targetDir) {
|
|
21581
|
-
const marvinDir =
|
|
22673
|
+
const marvinDir = path14.join(targetDir, ".marvin");
|
|
21582
22674
|
const { existsSync: existsSync17 } = await import("fs");
|
|
21583
22675
|
if (existsSync17(marvinDir)) {
|
|
21584
22676
|
throw new GitSyncError(
|
|
@@ -21758,13 +22850,13 @@ async function cloneCommand(url2, directory) {
|
|
|
21758
22850
|
}
|
|
21759
22851
|
|
|
21760
22852
|
// src/mcp/stdio-server.ts
|
|
21761
|
-
import * as
|
|
21762
|
-
import * as
|
|
22853
|
+
import * as fs14 from "fs";
|
|
22854
|
+
import * as path15 from "path";
|
|
21763
22855
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21764
22856
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
21765
22857
|
|
|
21766
22858
|
// src/skills/action-tools.ts
|
|
21767
|
-
import { tool as
|
|
22859
|
+
import { tool as tool23 } from "@anthropic-ai/claude-agent-sdk";
|
|
21768
22860
|
|
|
21769
22861
|
// src/skills/action-runner.ts
|
|
21770
22862
|
import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -21830,7 +22922,7 @@ function createSkillActionTools(skills, context) {
|
|
|
21830
22922
|
if (!skill.actions) continue;
|
|
21831
22923
|
for (const action of skill.actions) {
|
|
21832
22924
|
tools.push(
|
|
21833
|
-
|
|
22925
|
+
tool23(
|
|
21834
22926
|
`${skill.id}__${action.id}`,
|
|
21835
22927
|
action.description,
|
|
21836
22928
|
{
|
|
@@ -21922,10 +23014,10 @@ ${lines.join("\n\n")}`;
|
|
|
21922
23014
|
}
|
|
21923
23015
|
|
|
21924
23016
|
// src/mcp/persona-tools.ts
|
|
21925
|
-
import { tool as
|
|
23017
|
+
import { tool as tool24 } from "@anthropic-ai/claude-agent-sdk";
|
|
21926
23018
|
function createPersonaTools(ctx, marvinDir) {
|
|
21927
23019
|
return [
|
|
21928
|
-
|
|
23020
|
+
tool24(
|
|
21929
23021
|
"set_persona",
|
|
21930
23022
|
"Set the active persona for this session. Returns full guidance for the selected persona including behavioral rules, allowed document types, and scope. Call this before working to ensure persona-appropriate behavior.",
|
|
21931
23023
|
{
|
|
@@ -21955,7 +23047,7 @@ ${summaries}`
|
|
|
21955
23047
|
};
|
|
21956
23048
|
}
|
|
21957
23049
|
),
|
|
21958
|
-
|
|
23050
|
+
tool24(
|
|
21959
23051
|
"get_persona_guidance",
|
|
21960
23052
|
"Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
|
|
21961
23053
|
{
|
|
@@ -22057,8 +23149,8 @@ function collectTools(marvinDir) {
|
|
|
22057
23149
|
const plugin = resolvePlugin(config2.methodology);
|
|
22058
23150
|
const registrations = plugin?.documentTypeRegistrations ?? [];
|
|
22059
23151
|
const store = new DocumentStore(marvinDir, registrations);
|
|
22060
|
-
const sourcesDir =
|
|
22061
|
-
const hasSourcesDir =
|
|
23152
|
+
const sourcesDir = path15.join(marvinDir, "sources");
|
|
23153
|
+
const hasSourcesDir = fs14.existsSync(sourcesDir);
|
|
22062
23154
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
22063
23155
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
22064
23156
|
const sessionStore = new SessionStore(marvinDir);
|
|
@@ -22066,7 +23158,7 @@ function collectTools(marvinDir) {
|
|
|
22066
23158
|
const allSkillIds = [...allSkills.keys()];
|
|
22067
23159
|
const codeSkillTools = getSkillTools(allSkillIds, allSkills, store);
|
|
22068
23160
|
const skillsWithActions = allSkillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
|
|
22069
|
-
const projectRoot =
|
|
23161
|
+
const projectRoot = path15.dirname(marvinDir);
|
|
22070
23162
|
const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
|
|
22071
23163
|
const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
|
|
22072
23164
|
const navGroups = buildNavGroups({
|
|
@@ -22136,8 +23228,8 @@ async function serveCommand() {
|
|
|
22136
23228
|
}
|
|
22137
23229
|
|
|
22138
23230
|
// src/cli/commands/skills.ts
|
|
22139
|
-
import * as
|
|
22140
|
-
import * as
|
|
23231
|
+
import * as fs15 from "fs";
|
|
23232
|
+
import * as path16 from "path";
|
|
22141
23233
|
import * as YAML7 from "yaml";
|
|
22142
23234
|
import matter3 from "gray-matter";
|
|
22143
23235
|
import chalk10 from "chalk";
|
|
@@ -22243,14 +23335,14 @@ async function skillsRemoveCommand(skillId, options) {
|
|
|
22243
23335
|
}
|
|
22244
23336
|
async function skillsCreateCommand(name) {
|
|
22245
23337
|
const project = loadProject();
|
|
22246
|
-
const skillsDir =
|
|
22247
|
-
|
|
22248
|
-
const skillDir =
|
|
22249
|
-
if (
|
|
23338
|
+
const skillsDir = path16.join(project.marvinDir, "skills");
|
|
23339
|
+
fs15.mkdirSync(skillsDir, { recursive: true });
|
|
23340
|
+
const skillDir = path16.join(skillsDir, name);
|
|
23341
|
+
if (fs15.existsSync(skillDir)) {
|
|
22250
23342
|
console.log(chalk10.yellow(`Skill directory already exists: ${skillDir}`));
|
|
22251
23343
|
return;
|
|
22252
23344
|
}
|
|
22253
|
-
|
|
23345
|
+
fs15.mkdirSync(skillDir, { recursive: true });
|
|
22254
23346
|
const displayName = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
22255
23347
|
const frontmatter = {
|
|
22256
23348
|
name,
|
|
@@ -22264,7 +23356,7 @@ async function skillsCreateCommand(name) {
|
|
|
22264
23356
|
You have the **${displayName}** skill.
|
|
22265
23357
|
`;
|
|
22266
23358
|
const skillMd = matter3.stringify(body, frontmatter);
|
|
22267
|
-
|
|
23359
|
+
fs15.writeFileSync(path16.join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
22268
23360
|
const actions = [
|
|
22269
23361
|
{
|
|
22270
23362
|
id: "run",
|
|
@@ -22274,7 +23366,7 @@ You have the **${displayName}** skill.
|
|
|
22274
23366
|
maxTurns: 5
|
|
22275
23367
|
}
|
|
22276
23368
|
];
|
|
22277
|
-
|
|
23369
|
+
fs15.writeFileSync(path16.join(skillDir, "actions.yaml"), YAML7.stringify(actions), "utf-8");
|
|
22278
23370
|
console.log(chalk10.green(`Created skill: ${skillDir}/`));
|
|
22279
23371
|
console.log(chalk10.dim(" SKILL.md \u2014 skill definition and prompt"));
|
|
22280
23372
|
console.log(chalk10.dim(" actions.yaml \u2014 action definitions"));
|
|
@@ -22282,14 +23374,14 @@ You have the **${displayName}** skill.
|
|
|
22282
23374
|
}
|
|
22283
23375
|
async function skillsMigrateCommand() {
|
|
22284
23376
|
const project = loadProject();
|
|
22285
|
-
const skillsDir =
|
|
22286
|
-
if (!
|
|
23377
|
+
const skillsDir = path16.join(project.marvinDir, "skills");
|
|
23378
|
+
if (!fs15.existsSync(skillsDir)) {
|
|
22287
23379
|
console.log(chalk10.dim("No skills directory found."));
|
|
22288
23380
|
return;
|
|
22289
23381
|
}
|
|
22290
23382
|
let entries;
|
|
22291
23383
|
try {
|
|
22292
|
-
entries =
|
|
23384
|
+
entries = fs15.readdirSync(skillsDir);
|
|
22293
23385
|
} catch {
|
|
22294
23386
|
console.log(chalk10.red("Could not read skills directory."));
|
|
22295
23387
|
return;
|
|
@@ -22301,16 +23393,16 @@ async function skillsMigrateCommand() {
|
|
|
22301
23393
|
}
|
|
22302
23394
|
let migrated = 0;
|
|
22303
23395
|
for (const file2 of yamlFiles) {
|
|
22304
|
-
const yamlPath =
|
|
23396
|
+
const yamlPath = path16.join(skillsDir, file2);
|
|
22305
23397
|
const baseName = file2.replace(/\.(yaml|yml)$/, "");
|
|
22306
|
-
const outputDir =
|
|
22307
|
-
if (
|
|
23398
|
+
const outputDir = path16.join(skillsDir, baseName);
|
|
23399
|
+
if (fs15.existsSync(outputDir)) {
|
|
22308
23400
|
console.log(chalk10.yellow(`Skipping "${file2}" \u2014 directory "${baseName}/" already exists.`));
|
|
22309
23401
|
continue;
|
|
22310
23402
|
}
|
|
22311
23403
|
try {
|
|
22312
23404
|
migrateYamlToSkillMd(yamlPath, outputDir);
|
|
22313
|
-
|
|
23405
|
+
fs15.renameSync(yamlPath, `${yamlPath}.bak`);
|
|
22314
23406
|
console.log(chalk10.green(`Migrated "${file2}" \u2192 "${baseName}/"`));
|
|
22315
23407
|
migrated++;
|
|
22316
23408
|
} catch (err) {
|
|
@@ -22324,35 +23416,35 @@ ${migrated} skill(s) migrated. Original files renamed to *.bak`));
|
|
|
22324
23416
|
}
|
|
22325
23417
|
|
|
22326
23418
|
// src/cli/commands/import.ts
|
|
22327
|
-
import * as
|
|
22328
|
-
import * as
|
|
23419
|
+
import * as fs18 from "fs";
|
|
23420
|
+
import * as path19 from "path";
|
|
22329
23421
|
import chalk11 from "chalk";
|
|
22330
23422
|
|
|
22331
23423
|
// src/import/engine.ts
|
|
22332
|
-
import * as
|
|
22333
|
-
import * as
|
|
23424
|
+
import * as fs17 from "fs";
|
|
23425
|
+
import * as path18 from "path";
|
|
22334
23426
|
import matter5 from "gray-matter";
|
|
22335
23427
|
|
|
22336
23428
|
// src/import/classifier.ts
|
|
22337
|
-
import * as
|
|
22338
|
-
import * as
|
|
23429
|
+
import * as fs16 from "fs";
|
|
23430
|
+
import * as path17 from "path";
|
|
22339
23431
|
import matter4 from "gray-matter";
|
|
22340
23432
|
var RAW_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".txt"]);
|
|
22341
23433
|
var CORE_DIR_NAMES = /* @__PURE__ */ new Set(["decisions", "actions", "questions"]);
|
|
22342
23434
|
var ID_PATTERN = /^[A-Z]+-\d{3,}$/;
|
|
22343
23435
|
function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
22344
|
-
const resolved =
|
|
22345
|
-
const stat =
|
|
23436
|
+
const resolved = path17.resolve(inputPath);
|
|
23437
|
+
const stat = fs16.statSync(resolved);
|
|
22346
23438
|
if (!stat.isDirectory()) {
|
|
22347
23439
|
return classifyFile(resolved, knownTypes);
|
|
22348
23440
|
}
|
|
22349
|
-
if (
|
|
23441
|
+
if (path17.basename(resolved) === ".marvin" || fs16.existsSync(path17.join(resolved, "config.yaml"))) {
|
|
22350
23442
|
return { type: "marvin-project", inputPath: resolved };
|
|
22351
23443
|
}
|
|
22352
23444
|
const allDirNames = /* @__PURE__ */ new Set([...CORE_DIR_NAMES, ...knownDirNames]);
|
|
22353
|
-
const entries =
|
|
23445
|
+
const entries = fs16.readdirSync(resolved);
|
|
22354
23446
|
const hasDocSubdirs = entries.some(
|
|
22355
|
-
(e) => allDirNames.has(e) &&
|
|
23447
|
+
(e) => allDirNames.has(e) && fs16.statSync(path17.join(resolved, e)).isDirectory()
|
|
22356
23448
|
);
|
|
22357
23449
|
if (hasDocSubdirs) {
|
|
22358
23450
|
return { type: "docs-directory", inputPath: resolved };
|
|
@@ -22361,7 +23453,7 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22361
23453
|
if (mdFiles.length > 0) {
|
|
22362
23454
|
const hasMarvinDocs = mdFiles.some((f) => {
|
|
22363
23455
|
try {
|
|
22364
|
-
const raw =
|
|
23456
|
+
const raw = fs16.readFileSync(path17.join(resolved, f), "utf-8");
|
|
22365
23457
|
const { data } = matter4(raw);
|
|
22366
23458
|
return isValidMarvinDocument(data, knownTypes);
|
|
22367
23459
|
} catch {
|
|
@@ -22375,14 +23467,14 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22375
23467
|
return { type: "raw-source-dir", inputPath: resolved };
|
|
22376
23468
|
}
|
|
22377
23469
|
function classifyFile(filePath, knownTypes) {
|
|
22378
|
-
const resolved =
|
|
22379
|
-
const ext =
|
|
23470
|
+
const resolved = path17.resolve(filePath);
|
|
23471
|
+
const ext = path17.extname(resolved).toLowerCase();
|
|
22380
23472
|
if (RAW_SOURCE_EXTENSIONS.has(ext)) {
|
|
22381
23473
|
return { type: "raw-source-file", inputPath: resolved };
|
|
22382
23474
|
}
|
|
22383
23475
|
if (ext === ".md") {
|
|
22384
23476
|
try {
|
|
22385
|
-
const raw =
|
|
23477
|
+
const raw = fs16.readFileSync(resolved, "utf-8");
|
|
22386
23478
|
const { data } = matter4(raw);
|
|
22387
23479
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22388
23480
|
return { type: "marvin-document", inputPath: resolved };
|
|
@@ -22507,9 +23599,9 @@ function executeImportPlan(plan, store, marvinDir, options) {
|
|
|
22507
23599
|
continue;
|
|
22508
23600
|
}
|
|
22509
23601
|
if (item.action === "copy") {
|
|
22510
|
-
const targetDir =
|
|
22511
|
-
|
|
22512
|
-
|
|
23602
|
+
const targetDir = path18.dirname(item.targetPath);
|
|
23603
|
+
fs17.mkdirSync(targetDir, { recursive: true });
|
|
23604
|
+
fs17.copyFileSync(item.sourcePath, item.targetPath);
|
|
22513
23605
|
copied++;
|
|
22514
23606
|
continue;
|
|
22515
23607
|
}
|
|
@@ -22545,19 +23637,19 @@ function formatPlanSummary(plan) {
|
|
|
22545
23637
|
lines.push(`Documents to import: ${imports.length}`);
|
|
22546
23638
|
for (const item of imports) {
|
|
22547
23639
|
const idInfo = item.originalId !== item.newId ? `${item.originalId} \u2192 ${item.newId}` : item.newId ?? item.originalId ?? "";
|
|
22548
|
-
lines.push(` ${idInfo} ${
|
|
23640
|
+
lines.push(` ${idInfo} ${path18.basename(item.sourcePath)}`);
|
|
22549
23641
|
}
|
|
22550
23642
|
}
|
|
22551
23643
|
if (copies.length > 0) {
|
|
22552
23644
|
lines.push(`Files to copy to sources/: ${copies.length}`);
|
|
22553
23645
|
for (const item of copies) {
|
|
22554
|
-
lines.push(` ${
|
|
23646
|
+
lines.push(` ${path18.basename(item.sourcePath)} \u2192 ${path18.basename(item.targetPath)}`);
|
|
22555
23647
|
}
|
|
22556
23648
|
}
|
|
22557
23649
|
if (skips.length > 0) {
|
|
22558
23650
|
lines.push(`Skipped (conflict): ${skips.length}`);
|
|
22559
23651
|
for (const item of skips) {
|
|
22560
|
-
lines.push(` ${item.originalId ??
|
|
23652
|
+
lines.push(` ${item.originalId ?? path18.basename(item.sourcePath)} ${item.reason ?? ""}`);
|
|
22561
23653
|
}
|
|
22562
23654
|
}
|
|
22563
23655
|
if (plan.items.length === 0) {
|
|
@@ -22590,11 +23682,11 @@ function getDirNameForType(store, type) {
|
|
|
22590
23682
|
}
|
|
22591
23683
|
function collectMarvinDocs(dir, knownTypes) {
|
|
22592
23684
|
const docs = [];
|
|
22593
|
-
const files =
|
|
23685
|
+
const files = fs17.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
22594
23686
|
for (const file2 of files) {
|
|
22595
|
-
const filePath =
|
|
23687
|
+
const filePath = path18.join(dir, file2);
|
|
22596
23688
|
try {
|
|
22597
|
-
const raw =
|
|
23689
|
+
const raw = fs17.readFileSync(filePath, "utf-8");
|
|
22598
23690
|
const { data, content } = matter5(raw);
|
|
22599
23691
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22600
23692
|
docs.push({
|
|
@@ -22650,23 +23742,23 @@ function planDocImports(docs, store, options) {
|
|
|
22650
23742
|
}
|
|
22651
23743
|
function planFromMarvinProject(classification, store, _marvinDir, options) {
|
|
22652
23744
|
let projectDir = classification.inputPath;
|
|
22653
|
-
if (
|
|
22654
|
-
const inner =
|
|
22655
|
-
if (
|
|
23745
|
+
if (path18.basename(projectDir) !== ".marvin") {
|
|
23746
|
+
const inner = path18.join(projectDir, ".marvin");
|
|
23747
|
+
if (fs17.existsSync(inner)) {
|
|
22656
23748
|
projectDir = inner;
|
|
22657
23749
|
}
|
|
22658
23750
|
}
|
|
22659
|
-
const docsDir =
|
|
22660
|
-
if (!
|
|
23751
|
+
const docsDir = path18.join(projectDir, "docs");
|
|
23752
|
+
if (!fs17.existsSync(docsDir)) {
|
|
22661
23753
|
return [];
|
|
22662
23754
|
}
|
|
22663
23755
|
const knownTypes = store.registeredTypes;
|
|
22664
23756
|
const allDocs = [];
|
|
22665
|
-
const subdirs =
|
|
22666
|
-
(d) =>
|
|
23757
|
+
const subdirs = fs17.readdirSync(docsDir).filter(
|
|
23758
|
+
(d) => fs17.statSync(path18.join(docsDir, d)).isDirectory()
|
|
22667
23759
|
);
|
|
22668
23760
|
for (const subdir of subdirs) {
|
|
22669
|
-
const docs = collectMarvinDocs(
|
|
23761
|
+
const docs = collectMarvinDocs(path18.join(docsDir, subdir), knownTypes);
|
|
22670
23762
|
allDocs.push(...docs);
|
|
22671
23763
|
}
|
|
22672
23764
|
return planDocImports(allDocs, store, options);
|
|
@@ -22676,10 +23768,10 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22676
23768
|
const knownTypes = store.registeredTypes;
|
|
22677
23769
|
const allDocs = [];
|
|
22678
23770
|
allDocs.push(...collectMarvinDocs(dir, knownTypes));
|
|
22679
|
-
const entries =
|
|
23771
|
+
const entries = fs17.readdirSync(dir);
|
|
22680
23772
|
for (const entry of entries) {
|
|
22681
|
-
const entryPath =
|
|
22682
|
-
if (
|
|
23773
|
+
const entryPath = path18.join(dir, entry);
|
|
23774
|
+
if (fs17.statSync(entryPath).isDirectory()) {
|
|
22683
23775
|
allDocs.push(...collectMarvinDocs(entryPath, knownTypes));
|
|
22684
23776
|
}
|
|
22685
23777
|
}
|
|
@@ -22688,7 +23780,7 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22688
23780
|
function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
22689
23781
|
const filePath = classification.inputPath;
|
|
22690
23782
|
const knownTypes = store.registeredTypes;
|
|
22691
|
-
const raw =
|
|
23783
|
+
const raw = fs17.readFileSync(filePath, "utf-8");
|
|
22692
23784
|
const { data, content } = matter5(raw);
|
|
22693
23785
|
if (!isValidMarvinDocument(data, knownTypes)) {
|
|
22694
23786
|
return [];
|
|
@@ -22704,14 +23796,14 @@ function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
|
22704
23796
|
}
|
|
22705
23797
|
function planFromRawSourceDir(classification, marvinDir) {
|
|
22706
23798
|
const dir = classification.inputPath;
|
|
22707
|
-
const sourcesDir =
|
|
23799
|
+
const sourcesDir = path18.join(marvinDir, "sources");
|
|
22708
23800
|
const items = [];
|
|
22709
|
-
const files =
|
|
22710
|
-
const stat =
|
|
23801
|
+
const files = fs17.readdirSync(dir).filter((f) => {
|
|
23802
|
+
const stat = fs17.statSync(path18.join(dir, f));
|
|
22711
23803
|
return stat.isFile();
|
|
22712
23804
|
});
|
|
22713
23805
|
for (const file2 of files) {
|
|
22714
|
-
const sourcePath =
|
|
23806
|
+
const sourcePath = path18.join(dir, file2);
|
|
22715
23807
|
const targetPath = resolveSourceFileName(sourcesDir, file2);
|
|
22716
23808
|
items.push({
|
|
22717
23809
|
action: "copy",
|
|
@@ -22722,8 +23814,8 @@ function planFromRawSourceDir(classification, marvinDir) {
|
|
|
22722
23814
|
return items;
|
|
22723
23815
|
}
|
|
22724
23816
|
function planFromRawSourceFile(classification, marvinDir) {
|
|
22725
|
-
const sourcesDir =
|
|
22726
|
-
const fileName =
|
|
23817
|
+
const sourcesDir = path18.join(marvinDir, "sources");
|
|
23818
|
+
const fileName = path18.basename(classification.inputPath);
|
|
22727
23819
|
const targetPath = resolveSourceFileName(sourcesDir, fileName);
|
|
22728
23820
|
return [
|
|
22729
23821
|
{
|
|
@@ -22734,25 +23826,25 @@ function planFromRawSourceFile(classification, marvinDir) {
|
|
|
22734
23826
|
];
|
|
22735
23827
|
}
|
|
22736
23828
|
function resolveSourceFileName(sourcesDir, fileName) {
|
|
22737
|
-
const targetPath =
|
|
22738
|
-
if (!
|
|
23829
|
+
const targetPath = path18.join(sourcesDir, fileName);
|
|
23830
|
+
if (!fs17.existsSync(targetPath)) {
|
|
22739
23831
|
return targetPath;
|
|
22740
23832
|
}
|
|
22741
|
-
const ext =
|
|
22742
|
-
const base =
|
|
23833
|
+
const ext = path18.extname(fileName);
|
|
23834
|
+
const base = path18.basename(fileName, ext);
|
|
22743
23835
|
let counter = 1;
|
|
22744
23836
|
let candidate;
|
|
22745
23837
|
do {
|
|
22746
|
-
candidate =
|
|
23838
|
+
candidate = path18.join(sourcesDir, `${base}-${counter}${ext}`);
|
|
22747
23839
|
counter++;
|
|
22748
|
-
} while (
|
|
23840
|
+
} while (fs17.existsSync(candidate));
|
|
22749
23841
|
return candidate;
|
|
22750
23842
|
}
|
|
22751
23843
|
|
|
22752
23844
|
// src/cli/commands/import.ts
|
|
22753
23845
|
async function importCommand(inputPath, options) {
|
|
22754
|
-
const resolved =
|
|
22755
|
-
if (!
|
|
23846
|
+
const resolved = path19.resolve(inputPath);
|
|
23847
|
+
if (!fs18.existsSync(resolved)) {
|
|
22756
23848
|
throw new ImportError(`Path not found: ${resolved}`);
|
|
22757
23849
|
}
|
|
22758
23850
|
const project = loadProject();
|
|
@@ -22804,7 +23896,7 @@ async function importCommand(inputPath, options) {
|
|
|
22804
23896
|
console.log(chalk11.bold("\nStarting ingest of copied sources...\n"));
|
|
22805
23897
|
const manifest = new SourceManifestManager(marvinDir);
|
|
22806
23898
|
manifest.scan();
|
|
22807
|
-
const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) =>
|
|
23899
|
+
const copiedFileNames = result.items.filter((i) => i.action === "copy").map((i) => path19.basename(i.targetPath));
|
|
22808
23900
|
for (const fileName of copiedFileNames) {
|
|
22809
23901
|
try {
|
|
22810
23902
|
await ingestFile({
|
|
@@ -23693,14 +24785,14 @@ async function webCommand(options) {
|
|
|
23693
24785
|
}
|
|
23694
24786
|
|
|
23695
24787
|
// src/cli/commands/generate.ts
|
|
23696
|
-
import * as
|
|
23697
|
-
import * as
|
|
24788
|
+
import * as fs19 from "fs";
|
|
24789
|
+
import * as path20 from "path";
|
|
23698
24790
|
import chalk18 from "chalk";
|
|
23699
24791
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
23700
24792
|
async function generateClaudeMdCommand(options) {
|
|
23701
24793
|
const project = loadProject();
|
|
23702
|
-
const filePath =
|
|
23703
|
-
if (
|
|
24794
|
+
const filePath = path20.join(project.marvinDir, "CLAUDE.md");
|
|
24795
|
+
if (fs19.existsSync(filePath) && !options.force) {
|
|
23704
24796
|
const overwrite = await confirm2({
|
|
23705
24797
|
message: ".marvin/CLAUDE.md already exists. Overwrite?",
|
|
23706
24798
|
default: false
|
|
@@ -23710,7 +24802,7 @@ async function generateClaudeMdCommand(options) {
|
|
|
23710
24802
|
return;
|
|
23711
24803
|
}
|
|
23712
24804
|
}
|
|
23713
|
-
|
|
24805
|
+
fs19.writeFileSync(
|
|
23714
24806
|
filePath,
|
|
23715
24807
|
getDefaultClaudeMdContent(project.config.name),
|
|
23716
24808
|
"utf-8"
|
|
@@ -23723,7 +24815,7 @@ function createProgram() {
|
|
|
23723
24815
|
const program2 = new Command();
|
|
23724
24816
|
program2.name("marvin").description(
|
|
23725
24817
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
23726
|
-
).version("0.4.
|
|
24818
|
+
).version("0.4.4");
|
|
23727
24819
|
program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
23728
24820
|
await initCommand();
|
|
23729
24821
|
});
|