mrvn-cli 0.4.1 → 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 +1436 -323
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +1328 -215
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +1440 -327
- 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
|
}
|
|
@@ -14105,7 +14105,14 @@ import { tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
|
|
|
14105
14105
|
// src/plugins/builtin/tools/epic-utils.ts
|
|
14106
14106
|
function normalizeLinkedFeatures(value) {
|
|
14107
14107
|
if (value === void 0 || value === null) return [];
|
|
14108
|
-
if (typeof value === "string")
|
|
14108
|
+
if (typeof value === "string") {
|
|
14109
|
+
try {
|
|
14110
|
+
const parsed = JSON.parse(value);
|
|
14111
|
+
if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
|
|
14112
|
+
} catch {
|
|
14113
|
+
}
|
|
14114
|
+
return [value];
|
|
14115
|
+
}
|
|
14109
14116
|
if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
|
|
14110
14117
|
return [];
|
|
14111
14118
|
}
|
|
@@ -14928,6 +14935,20 @@ function createFeatureTools(store) {
|
|
|
14928
14935
|
|
|
14929
14936
|
// src/plugins/builtin/tools/epics.ts
|
|
14930
14937
|
import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
|
|
14938
|
+
var linkedFeatureArray = external_exports.preprocess(
|
|
14939
|
+
(val) => {
|
|
14940
|
+
if (typeof val === "string") {
|
|
14941
|
+
try {
|
|
14942
|
+
const parsed = JSON.parse(val);
|
|
14943
|
+
if (Array.isArray(parsed)) return parsed;
|
|
14944
|
+
} catch {
|
|
14945
|
+
}
|
|
14946
|
+
return [val];
|
|
14947
|
+
}
|
|
14948
|
+
return val;
|
|
14949
|
+
},
|
|
14950
|
+
external_exports.array(external_exports.string())
|
|
14951
|
+
);
|
|
14931
14952
|
function createEpicTools(store) {
|
|
14932
14953
|
return [
|
|
14933
14954
|
tool4(
|
|
@@ -14993,7 +15014,7 @@ function createEpicTools(store) {
|
|
|
14993
15014
|
{
|
|
14994
15015
|
title: external_exports.string().describe("Epic title"),
|
|
14995
15016
|
content: external_exports.string().describe("Epic description and scope"),
|
|
14996
|
-
linkedFeature:
|
|
15017
|
+
linkedFeature: linkedFeatureArray.describe("Feature ID(s) to link this epic to (e.g. ['F-001'] or ['F-001', 'F-002'])"),
|
|
14997
15018
|
status: external_exports.enum(["planned", "in-progress", "done"]).optional().describe("Epic status (default: 'planned')"),
|
|
14998
15019
|
owner: external_exports.string().optional().describe("Epic owner"),
|
|
14999
15020
|
targetDate: external_exports.string().optional().describe("Target completion date (ISO format)"),
|
|
@@ -15069,7 +15090,7 @@ function createEpicTools(store) {
|
|
|
15069
15090
|
owner: external_exports.string().optional().describe("New owner"),
|
|
15070
15091
|
targetDate: external_exports.string().optional().describe("New target date"),
|
|
15071
15092
|
estimatedEffort: external_exports.string().optional().describe("New estimated effort"),
|
|
15072
|
-
linkedFeature:
|
|
15093
|
+
linkedFeature: linkedFeatureArray.optional().describe("New linked feature ID(s)"),
|
|
15073
15094
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
15074
15095
|
},
|
|
15075
15096
|
async (args) => {
|
|
@@ -15606,6 +15627,205 @@ function createSprintPlanningTools(store) {
|
|
|
15606
15627
|
];
|
|
15607
15628
|
}
|
|
15608
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
|
+
|
|
15609
15829
|
// src/plugins/common.ts
|
|
15610
15830
|
var COMMON_REGISTRATIONS = [
|
|
15611
15831
|
{ type: "meeting", dirName: "meetings", idPrefix: "M" },
|
|
@@ -15613,7 +15833,8 @@ var COMMON_REGISTRATIONS = [
|
|
|
15613
15833
|
{ type: "feature", dirName: "features", idPrefix: "F" },
|
|
15614
15834
|
{ type: "epic", dirName: "epics", idPrefix: "E" },
|
|
15615
15835
|
{ type: "contribution", dirName: "contributions", idPrefix: "C" },
|
|
15616
|
-
{ type: "sprint", dirName: "sprints", idPrefix: "SP" }
|
|
15836
|
+
{ type: "sprint", dirName: "sprints", idPrefix: "SP" },
|
|
15837
|
+
{ type: "task", dirName: "tasks", idPrefix: "T" }
|
|
15617
15838
|
];
|
|
15618
15839
|
function createCommonTools(store) {
|
|
15619
15840
|
return [
|
|
@@ -15623,7 +15844,8 @@ function createCommonTools(store) {
|
|
|
15623
15844
|
...createEpicTools(store),
|
|
15624
15845
|
...createContributionTools(store),
|
|
15625
15846
|
...createSprintTools(store),
|
|
15626
|
-
...createSprintPlanningTools(store)
|
|
15847
|
+
...createSprintPlanningTools(store),
|
|
15848
|
+
...createTaskTools(store)
|
|
15627
15849
|
];
|
|
15628
15850
|
}
|
|
15629
15851
|
|
|
@@ -15633,7 +15855,7 @@ var genericAgilePlugin = {
|
|
|
15633
15855
|
name: "Generic Agile",
|
|
15634
15856
|
description: "Default methodology plugin providing standard agile governance patterns for decisions, actions, and questions.",
|
|
15635
15857
|
version: "0.1.0",
|
|
15636
|
-
documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic", "contribution", "sprint"],
|
|
15858
|
+
documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic", "contribution", "sprint", "task"],
|
|
15637
15859
|
documentTypeRegistrations: [...COMMON_REGISTRATIONS],
|
|
15638
15860
|
tools: (store) => [...createCommonTools(store)],
|
|
15639
15861
|
promptFragments: {
|
|
@@ -15672,6 +15894,11 @@ var genericAgilePlugin = {
|
|
|
15672
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.
|
|
15673
15895
|
- **update_epic**: Update epic status (planned \u2192 in-progress \u2192 done), owner, and other fields.
|
|
15674
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
|
+
|
|
15675
15902
|
**Feature Tools (read-only for awareness):**
|
|
15676
15903
|
- **list_features** / **get_feature**: View features to understand what needs to be broken into epics.
|
|
15677
15904
|
|
|
@@ -15683,6 +15910,7 @@ var genericAgilePlugin = {
|
|
|
15683
15910
|
|
|
15684
15911
|
**Key Workflow Rules:**
|
|
15685
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.
|
|
15686
15914
|
- Tag work items (actions, decisions, questions) with \`epic:E-xxx\` to group them under an epic.
|
|
15687
15915
|
- Collaborate with the Delivery Manager on target dates and effort estimates.
|
|
15688
15916
|
- Each epic should have a clear scope and definition of done.
|
|
@@ -15718,6 +15946,9 @@ var genericAgilePlugin = {
|
|
|
15718
15946
|
- **list_epics** / **get_epic**: View epics and their current status.
|
|
15719
15947
|
- **update_epic**: Set targetDate and estimatedEffort on epics. Flag epics linked to deferred features.
|
|
15720
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
|
+
|
|
15721
15952
|
**Feature Tools (tracking focus):**
|
|
15722
15953
|
- **list_features** / **get_feature**: View features and their priorities.
|
|
15723
15954
|
|
|
@@ -15763,14 +15994,15 @@ var genericAgilePlugin = {
|
|
|
15763
15994
|
- Reason through: priority (critical/high features first), capacity (compare backlog effort to velocity reference), dependencies and blockers, balance across features, and risk.
|
|
15764
15995
|
- Present a structured sprint proposal: title, goal, suggested dates, selected epics with rationale for each, excluded epics with reason, and identified risks.
|
|
15765
15996
|
- After user confirmation, use **create_sprint** with the agreed epics to persist the sprint.`,
|
|
15766
|
-
"*": `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:
|
|
15767
15998
|
|
|
15768
15999
|
**Features** (F-xxx): Product capabilities defined by the Product Owner. Features progress through draft \u2192 approved \u2192 done.
|
|
15769
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.
|
|
15770
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).
|
|
15771
16003
|
**Meetings**: Meeting records with attendees, agendas, and notes.
|
|
15772
16004
|
|
|
15773
|
-
**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.
|
|
15774
16006
|
|
|
15775
16007
|
- **list_meetings** / **get_meeting**: Browse and read meeting records.
|
|
15776
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.
|
|
@@ -15785,10 +16017,10 @@ var genericAgilePlugin = {
|
|
|
15785
16017
|
};
|
|
15786
16018
|
|
|
15787
16019
|
// src/plugins/builtin/tools/use-cases.ts
|
|
15788
|
-
import { tool as
|
|
16020
|
+
import { tool as tool9 } from "@anthropic-ai/claude-agent-sdk";
|
|
15789
16021
|
function createUseCaseTools(store) {
|
|
15790
16022
|
return [
|
|
15791
|
-
|
|
16023
|
+
tool9(
|
|
15792
16024
|
"list_use_cases",
|
|
15793
16025
|
"List all extension use cases, optionally filtered by status or extension type",
|
|
15794
16026
|
{
|
|
@@ -15818,7 +16050,7 @@ function createUseCaseTools(store) {
|
|
|
15818
16050
|
},
|
|
15819
16051
|
{ annotations: { readOnlyHint: true } }
|
|
15820
16052
|
),
|
|
15821
|
-
|
|
16053
|
+
tool9(
|
|
15822
16054
|
"get_use_case",
|
|
15823
16055
|
"Get the full content of a specific use case by ID",
|
|
15824
16056
|
{ id: external_exports.string().describe("Use case ID (e.g. 'UC-001')") },
|
|
@@ -15845,7 +16077,7 @@ function createUseCaseTools(store) {
|
|
|
15845
16077
|
},
|
|
15846
16078
|
{ annotations: { readOnlyHint: true } }
|
|
15847
16079
|
),
|
|
15848
|
-
|
|
16080
|
+
tool9(
|
|
15849
16081
|
"create_use_case",
|
|
15850
16082
|
"Create a new extension use case definition (Phase 1: Assess Extension Use Case)",
|
|
15851
16083
|
{
|
|
@@ -15879,7 +16111,7 @@ function createUseCaseTools(store) {
|
|
|
15879
16111
|
};
|
|
15880
16112
|
}
|
|
15881
16113
|
),
|
|
15882
|
-
|
|
16114
|
+
tool9(
|
|
15883
16115
|
"update_use_case",
|
|
15884
16116
|
"Update an existing extension use case",
|
|
15885
16117
|
{
|
|
@@ -15909,10 +16141,10 @@ function createUseCaseTools(store) {
|
|
|
15909
16141
|
}
|
|
15910
16142
|
|
|
15911
16143
|
// src/plugins/builtin/tools/tech-assessments.ts
|
|
15912
|
-
import { tool as
|
|
16144
|
+
import { tool as tool10 } from "@anthropic-ai/claude-agent-sdk";
|
|
15913
16145
|
function createTechAssessmentTools(store) {
|
|
15914
16146
|
return [
|
|
15915
|
-
|
|
16147
|
+
tool10(
|
|
15916
16148
|
"list_tech_assessments",
|
|
15917
16149
|
"List all technology assessments, optionally filtered by status",
|
|
15918
16150
|
{
|
|
@@ -15943,7 +16175,7 @@ function createTechAssessmentTools(store) {
|
|
|
15943
16175
|
},
|
|
15944
16176
|
{ annotations: { readOnlyHint: true } }
|
|
15945
16177
|
),
|
|
15946
|
-
|
|
16178
|
+
tool10(
|
|
15947
16179
|
"get_tech_assessment",
|
|
15948
16180
|
"Get the full content of a specific technology assessment by ID",
|
|
15949
16181
|
{ id: external_exports.string().describe("Tech assessment ID (e.g. 'TA-001')") },
|
|
@@ -15970,7 +16202,7 @@ function createTechAssessmentTools(store) {
|
|
|
15970
16202
|
},
|
|
15971
16203
|
{ annotations: { readOnlyHint: true } }
|
|
15972
16204
|
),
|
|
15973
|
-
|
|
16205
|
+
tool10(
|
|
15974
16206
|
"create_tech_assessment",
|
|
15975
16207
|
"Create a new technology assessment linked to an assessed/approved use case (Phase 2: Assess Extension Technology)",
|
|
15976
16208
|
{
|
|
@@ -16041,7 +16273,7 @@ function createTechAssessmentTools(store) {
|
|
|
16041
16273
|
};
|
|
16042
16274
|
}
|
|
16043
16275
|
),
|
|
16044
|
-
|
|
16276
|
+
tool10(
|
|
16045
16277
|
"update_tech_assessment",
|
|
16046
16278
|
"Update an existing technology assessment. The linked use case cannot be changed.",
|
|
16047
16279
|
{
|
|
@@ -16071,10 +16303,10 @@ function createTechAssessmentTools(store) {
|
|
|
16071
16303
|
}
|
|
16072
16304
|
|
|
16073
16305
|
// src/plugins/builtin/tools/extension-designs.ts
|
|
16074
|
-
import { tool as
|
|
16306
|
+
import { tool as tool11 } from "@anthropic-ai/claude-agent-sdk";
|
|
16075
16307
|
function createExtensionDesignTools(store) {
|
|
16076
16308
|
return [
|
|
16077
|
-
|
|
16309
|
+
tool11(
|
|
16078
16310
|
"list_extension_designs",
|
|
16079
16311
|
"List all extension designs, optionally filtered by status",
|
|
16080
16312
|
{
|
|
@@ -16104,7 +16336,7 @@ function createExtensionDesignTools(store) {
|
|
|
16104
16336
|
},
|
|
16105
16337
|
{ annotations: { readOnlyHint: true } }
|
|
16106
16338
|
),
|
|
16107
|
-
|
|
16339
|
+
tool11(
|
|
16108
16340
|
"get_extension_design",
|
|
16109
16341
|
"Get the full content of a specific extension design by ID",
|
|
16110
16342
|
{ id: external_exports.string().describe("Extension design ID (e.g. 'XD-001')") },
|
|
@@ -16131,7 +16363,7 @@ function createExtensionDesignTools(store) {
|
|
|
16131
16363
|
},
|
|
16132
16364
|
{ annotations: { readOnlyHint: true } }
|
|
16133
16365
|
),
|
|
16134
|
-
|
|
16366
|
+
tool11(
|
|
16135
16367
|
"create_extension_design",
|
|
16136
16368
|
"Create a new extension design linked to a recommended tech assessment (Phase 3: Define Extension Target Solution)",
|
|
16137
16369
|
{
|
|
@@ -16199,7 +16431,7 @@ function createExtensionDesignTools(store) {
|
|
|
16199
16431
|
};
|
|
16200
16432
|
}
|
|
16201
16433
|
),
|
|
16202
|
-
|
|
16434
|
+
tool11(
|
|
16203
16435
|
"update_extension_design",
|
|
16204
16436
|
"Update an existing extension design. The linked tech assessment cannot be changed.",
|
|
16205
16437
|
{
|
|
@@ -16228,10 +16460,10 @@ function createExtensionDesignTools(store) {
|
|
|
16228
16460
|
}
|
|
16229
16461
|
|
|
16230
16462
|
// src/plugins/builtin/tools/aem-reports.ts
|
|
16231
|
-
import { tool as
|
|
16463
|
+
import { tool as tool12 } from "@anthropic-ai/claude-agent-sdk";
|
|
16232
16464
|
function createAemReportTools(store) {
|
|
16233
16465
|
return [
|
|
16234
|
-
|
|
16466
|
+
tool12(
|
|
16235
16467
|
"generate_extension_portfolio",
|
|
16236
16468
|
"Generate a portfolio view of all use cases with their linked tech assessments and extension designs",
|
|
16237
16469
|
{},
|
|
@@ -16283,7 +16515,7 @@ function createAemReportTools(store) {
|
|
|
16283
16515
|
},
|
|
16284
16516
|
{ annotations: { readOnlyHint: true } }
|
|
16285
16517
|
),
|
|
16286
|
-
|
|
16518
|
+
tool12(
|
|
16287
16519
|
"generate_tech_readiness",
|
|
16288
16520
|
"Generate a BTP technology readiness report showing service coverage and gaps across assessments",
|
|
16289
16521
|
{},
|
|
@@ -16335,7 +16567,7 @@ function createAemReportTools(store) {
|
|
|
16335
16567
|
},
|
|
16336
16568
|
{ annotations: { readOnlyHint: true } }
|
|
16337
16569
|
),
|
|
16338
|
-
|
|
16570
|
+
tool12(
|
|
16339
16571
|
"generate_phase_status",
|
|
16340
16572
|
"Generate a phase progress report showing artifact counts and readiness per AEM phase",
|
|
16341
16573
|
{},
|
|
@@ -16397,11 +16629,11 @@ function createAemReportTools(store) {
|
|
|
16397
16629
|
import * as fs3 from "fs";
|
|
16398
16630
|
import * as path3 from "path";
|
|
16399
16631
|
import * as YAML2 from "yaml";
|
|
16400
|
-
import { tool as
|
|
16632
|
+
import { tool as tool13 } from "@anthropic-ai/claude-agent-sdk";
|
|
16401
16633
|
var PHASES = ["assess-use-case", "assess-technology", "define-solution"];
|
|
16402
16634
|
function createAemPhaseTools(store, marvinDir) {
|
|
16403
16635
|
return [
|
|
16404
|
-
|
|
16636
|
+
tool13(
|
|
16405
16637
|
"get_current_phase",
|
|
16406
16638
|
"Get the current AEM phase from project configuration",
|
|
16407
16639
|
{},
|
|
@@ -16422,7 +16654,7 @@ function createAemPhaseTools(store, marvinDir) {
|
|
|
16422
16654
|
},
|
|
16423
16655
|
{ annotations: { readOnlyHint: true } }
|
|
16424
16656
|
),
|
|
16425
|
-
|
|
16657
|
+
tool13(
|
|
16426
16658
|
"advance_phase",
|
|
16427
16659
|
"Advance to the next AEM phase. Performs soft gate checks and warns if artifacts are incomplete, but does not block.",
|
|
16428
16660
|
{
|
|
@@ -16931,7 +17163,7 @@ var deliveryManager = {
|
|
|
16931
17163
|
"Epic scheduling and tracking",
|
|
16932
17164
|
"Sprint planning and tracking"
|
|
16933
17165
|
],
|
|
16934
|
-
documentTypes: ["action", "decision", "meeting", "question", "feature", "epic", "sprint"],
|
|
17166
|
+
documentTypes: ["action", "decision", "meeting", "question", "feature", "epic", "task", "sprint"],
|
|
16935
17167
|
contributionTypes: ["risk-finding", "blocker-report", "dependency-update", "status-assessment"]
|
|
16936
17168
|
};
|
|
16937
17169
|
|
|
@@ -16969,9 +17201,10 @@ var techLead = {
|
|
|
16969
17201
|
"Implementation guidance",
|
|
16970
17202
|
"Non-functional requirements",
|
|
16971
17203
|
"Epic creation and scoping",
|
|
17204
|
+
"Task creation and breakdown",
|
|
16972
17205
|
"Sprint scoping and technical execution"
|
|
16973
17206
|
],
|
|
16974
|
-
documentTypes: ["decision", "action", "question", "epic", "sprint"],
|
|
17207
|
+
documentTypes: ["decision", "action", "question", "epic", "task", "sprint"],
|
|
16975
17208
|
contributionTypes: ["action-result", "spike-findings", "technical-assessment", "architecture-review"]
|
|
16976
17209
|
};
|
|
16977
17210
|
|
|
@@ -17004,8 +17237,8 @@ function resolvePersonaId(input4) {
|
|
|
17004
17237
|
}
|
|
17005
17238
|
|
|
17006
17239
|
// src/agent/session.ts
|
|
17007
|
-
import * as
|
|
17008
|
-
import * as
|
|
17240
|
+
import * as fs11 from "fs";
|
|
17241
|
+
import * as path11 from "path";
|
|
17009
17242
|
import * as readline from "readline";
|
|
17010
17243
|
import chalk2 from "chalk";
|
|
17011
17244
|
import ora from "ora";
|
|
@@ -17373,10 +17606,10 @@ import {
|
|
|
17373
17606
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
17374
17607
|
|
|
17375
17608
|
// src/agent/tools/decisions.ts
|
|
17376
|
-
import { tool as
|
|
17609
|
+
import { tool as tool14 } from "@anthropic-ai/claude-agent-sdk";
|
|
17377
17610
|
function createDecisionTools(store) {
|
|
17378
17611
|
return [
|
|
17379
|
-
|
|
17612
|
+
tool14(
|
|
17380
17613
|
"list_decisions",
|
|
17381
17614
|
"List all decisions in the project, optionally filtered by status",
|
|
17382
17615
|
{ status: external_exports.string().optional().describe("Filter by status (e.g. 'open', 'decided', 'superseded')") },
|
|
@@ -17394,7 +17627,7 @@ function createDecisionTools(store) {
|
|
|
17394
17627
|
},
|
|
17395
17628
|
{ annotations: { readOnlyHint: true } }
|
|
17396
17629
|
),
|
|
17397
|
-
|
|
17630
|
+
tool14(
|
|
17398
17631
|
"get_decision",
|
|
17399
17632
|
"Get the full content of a specific decision by ID",
|
|
17400
17633
|
{ id: external_exports.string().describe("Decision ID (e.g. 'D-001')") },
|
|
@@ -17421,7 +17654,7 @@ function createDecisionTools(store) {
|
|
|
17421
17654
|
},
|
|
17422
17655
|
{ annotations: { readOnlyHint: true } }
|
|
17423
17656
|
),
|
|
17424
|
-
|
|
17657
|
+
tool14(
|
|
17425
17658
|
"create_decision",
|
|
17426
17659
|
"Create a new decision record",
|
|
17427
17660
|
{
|
|
@@ -17452,7 +17685,7 @@ function createDecisionTools(store) {
|
|
|
17452
17685
|
};
|
|
17453
17686
|
}
|
|
17454
17687
|
),
|
|
17455
|
-
|
|
17688
|
+
tool14(
|
|
17456
17689
|
"update_decision",
|
|
17457
17690
|
"Update an existing decision",
|
|
17458
17691
|
{
|
|
@@ -17480,7 +17713,7 @@ function createDecisionTools(store) {
|
|
|
17480
17713
|
}
|
|
17481
17714
|
|
|
17482
17715
|
// src/agent/tools/actions.ts
|
|
17483
|
-
import { tool as
|
|
17716
|
+
import { tool as tool15 } from "@anthropic-ai/claude-agent-sdk";
|
|
17484
17717
|
function findMatchingSprints(store, dueDate) {
|
|
17485
17718
|
const sprints = store.list({ type: "sprint" });
|
|
17486
17719
|
return sprints.filter((s) => {
|
|
@@ -17496,7 +17729,7 @@ function findMatchingSprints(store, dueDate) {
|
|
|
17496
17729
|
}
|
|
17497
17730
|
function createActionTools(store) {
|
|
17498
17731
|
return [
|
|
17499
|
-
|
|
17732
|
+
tool15(
|
|
17500
17733
|
"list_actions",
|
|
17501
17734
|
"List all action items in the project, optionally filtered by status or owner",
|
|
17502
17735
|
{
|
|
@@ -17528,7 +17761,7 @@ function createActionTools(store) {
|
|
|
17528
17761
|
},
|
|
17529
17762
|
{ annotations: { readOnlyHint: true } }
|
|
17530
17763
|
),
|
|
17531
|
-
|
|
17764
|
+
tool15(
|
|
17532
17765
|
"get_action",
|
|
17533
17766
|
"Get the full content of a specific action item by ID",
|
|
17534
17767
|
{ id: external_exports.string().describe("Action ID (e.g. 'A-001')") },
|
|
@@ -17555,7 +17788,7 @@ function createActionTools(store) {
|
|
|
17555
17788
|
},
|
|
17556
17789
|
{ annotations: { readOnlyHint: true } }
|
|
17557
17790
|
),
|
|
17558
|
-
|
|
17791
|
+
tool15(
|
|
17559
17792
|
"create_action",
|
|
17560
17793
|
"Create a new action item",
|
|
17561
17794
|
{
|
|
@@ -17601,7 +17834,7 @@ function createActionTools(store) {
|
|
|
17601
17834
|
};
|
|
17602
17835
|
}
|
|
17603
17836
|
),
|
|
17604
|
-
|
|
17837
|
+
tool15(
|
|
17605
17838
|
"update_action",
|
|
17606
17839
|
"Update an existing action item",
|
|
17607
17840
|
{
|
|
@@ -17650,7 +17883,7 @@ function createActionTools(store) {
|
|
|
17650
17883
|
};
|
|
17651
17884
|
}
|
|
17652
17885
|
),
|
|
17653
|
-
|
|
17886
|
+
tool15(
|
|
17654
17887
|
"suggest_sprints_for_action",
|
|
17655
17888
|
"Suggest sprints whose date range contains the given due date. Helps assign actions to the right sprint.",
|
|
17656
17889
|
{
|
|
@@ -17683,10 +17916,10 @@ function createActionTools(store) {
|
|
|
17683
17916
|
}
|
|
17684
17917
|
|
|
17685
17918
|
// src/agent/tools/questions.ts
|
|
17686
|
-
import { tool as
|
|
17919
|
+
import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
|
|
17687
17920
|
function createQuestionTools(store) {
|
|
17688
17921
|
return [
|
|
17689
|
-
|
|
17922
|
+
tool16(
|
|
17690
17923
|
"list_questions",
|
|
17691
17924
|
"List all questions in the project, optionally filtered by status",
|
|
17692
17925
|
{
|
|
@@ -17707,7 +17940,7 @@ function createQuestionTools(store) {
|
|
|
17707
17940
|
},
|
|
17708
17941
|
{ annotations: { readOnlyHint: true } }
|
|
17709
17942
|
),
|
|
17710
|
-
|
|
17943
|
+
tool16(
|
|
17711
17944
|
"get_question",
|
|
17712
17945
|
"Get the full content of a specific question by ID",
|
|
17713
17946
|
{ id: external_exports.string().describe("Question ID (e.g. 'Q-001')") },
|
|
@@ -17734,7 +17967,7 @@ function createQuestionTools(store) {
|
|
|
17734
17967
|
},
|
|
17735
17968
|
{ annotations: { readOnlyHint: true } }
|
|
17736
17969
|
),
|
|
17737
|
-
|
|
17970
|
+
tool16(
|
|
17738
17971
|
"create_question",
|
|
17739
17972
|
"Create a new question that needs to be answered",
|
|
17740
17973
|
{
|
|
@@ -17765,7 +17998,7 @@ function createQuestionTools(store) {
|
|
|
17765
17998
|
};
|
|
17766
17999
|
}
|
|
17767
18000
|
),
|
|
17768
|
-
|
|
18001
|
+
tool16(
|
|
17769
18002
|
"update_question",
|
|
17770
18003
|
"Update an existing question",
|
|
17771
18004
|
{
|
|
@@ -17793,10 +18026,10 @@ function createQuestionTools(store) {
|
|
|
17793
18026
|
}
|
|
17794
18027
|
|
|
17795
18028
|
// src/agent/tools/documents.ts
|
|
17796
|
-
import { tool as
|
|
18029
|
+
import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
|
|
17797
18030
|
function createDocumentTools(store) {
|
|
17798
18031
|
return [
|
|
17799
|
-
|
|
18032
|
+
tool17(
|
|
17800
18033
|
"search_documents",
|
|
17801
18034
|
"Search all project documents, optionally filtered by type, status, or tag",
|
|
17802
18035
|
{
|
|
@@ -17828,7 +18061,7 @@ function createDocumentTools(store) {
|
|
|
17828
18061
|
},
|
|
17829
18062
|
{ annotations: { readOnlyHint: true } }
|
|
17830
18063
|
),
|
|
17831
|
-
|
|
18064
|
+
tool17(
|
|
17832
18065
|
"read_document",
|
|
17833
18066
|
"Read the full content of any project document by ID",
|
|
17834
18067
|
{ id: external_exports.string().describe("Document ID (e.g. 'D-001', 'A-003', 'Q-002')") },
|
|
@@ -17855,7 +18088,7 @@ function createDocumentTools(store) {
|
|
|
17855
18088
|
},
|
|
17856
18089
|
{ annotations: { readOnlyHint: true } }
|
|
17857
18090
|
),
|
|
17858
|
-
|
|
18091
|
+
tool17(
|
|
17859
18092
|
"project_summary",
|
|
17860
18093
|
"Get a summary of all project documents and their counts",
|
|
17861
18094
|
{},
|
|
@@ -17887,10 +18120,10 @@ function createDocumentTools(store) {
|
|
|
17887
18120
|
}
|
|
17888
18121
|
|
|
17889
18122
|
// src/agent/tools/sources.ts
|
|
17890
|
-
import { tool as
|
|
18123
|
+
import { tool as tool18 } from "@anthropic-ai/claude-agent-sdk";
|
|
17891
18124
|
function createSourceTools(manifest) {
|
|
17892
18125
|
return [
|
|
17893
|
-
|
|
18126
|
+
tool18(
|
|
17894
18127
|
"list_sources",
|
|
17895
18128
|
"List all source documents and their processing status",
|
|
17896
18129
|
{
|
|
@@ -17920,7 +18153,7 @@ function createSourceTools(manifest) {
|
|
|
17920
18153
|
},
|
|
17921
18154
|
{ annotations: { readOnlyHint: true } }
|
|
17922
18155
|
),
|
|
17923
|
-
|
|
18156
|
+
tool18(
|
|
17924
18157
|
"get_source_info",
|
|
17925
18158
|
"Get detailed information about a specific source document",
|
|
17926
18159
|
{
|
|
@@ -17958,10 +18191,10 @@ function createSourceTools(manifest) {
|
|
|
17958
18191
|
}
|
|
17959
18192
|
|
|
17960
18193
|
// src/agent/tools/sessions.ts
|
|
17961
|
-
import { tool as
|
|
18194
|
+
import { tool as tool19 } from "@anthropic-ai/claude-agent-sdk";
|
|
17962
18195
|
function createSessionTools(store) {
|
|
17963
18196
|
return [
|
|
17964
|
-
|
|
18197
|
+
tool19(
|
|
17965
18198
|
"list_sessions",
|
|
17966
18199
|
"List all saved chat sessions, sorted by most recently used",
|
|
17967
18200
|
{
|
|
@@ -17985,7 +18218,7 @@ function createSessionTools(store) {
|
|
|
17985
18218
|
},
|
|
17986
18219
|
{ annotations: { readOnlyHint: true } }
|
|
17987
18220
|
),
|
|
17988
|
-
|
|
18221
|
+
tool19(
|
|
17989
18222
|
"get_session",
|
|
17990
18223
|
"Get details of a specific saved session by name",
|
|
17991
18224
|
{ name: external_exports.string().describe("Session name (e.g. 'jwt-auth-decision')") },
|
|
@@ -18008,7 +18241,7 @@ function createSessionTools(store) {
|
|
|
18008
18241
|
|
|
18009
18242
|
// src/agent/tools/web.ts
|
|
18010
18243
|
import * as http2 from "http";
|
|
18011
|
-
import { tool as
|
|
18244
|
+
import { tool as tool22 } from "@anthropic-ai/claude-agent-sdk";
|
|
18012
18245
|
|
|
18013
18246
|
// src/web/data.ts
|
|
18014
18247
|
function getOverviewData(store) {
|
|
@@ -18244,6 +18477,7 @@ function inline(text) {
|
|
|
18244
18477
|
function layout(opts, body) {
|
|
18245
18478
|
const topItems = [
|
|
18246
18479
|
{ href: "/", label: "Overview" },
|
|
18480
|
+
{ href: "/timeline", label: "Timeline" },
|
|
18247
18481
|
{ href: "/board", label: "Board" },
|
|
18248
18482
|
{ href: "/gar", label: "GAR Report" },
|
|
18249
18483
|
{ href: "/health", label: "Health" }
|
|
@@ -18280,7 +18514,7 @@ function layout(opts, body) {
|
|
|
18280
18514
|
${groupsHtml}
|
|
18281
18515
|
</nav>
|
|
18282
18516
|
</aside>
|
|
18283
|
-
<main class="main">
|
|
18517
|
+
<main class="main${opts.mainClass ? ` ${opts.mainClass}` : ""}">
|
|
18284
18518
|
<button class="expand-toggle" onclick="document.querySelector('.main').classList.toggle('expanded')" title="Toggle wide view">
|
|
18285
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>
|
|
18286
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>
|
|
@@ -18289,7 +18523,36 @@ function layout(opts, body) {
|
|
|
18289
18523
|
</main>
|
|
18290
18524
|
</div>
|
|
18291
18525
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
18292
|
-
<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>
|
|
18293
18556
|
</body>
|
|
18294
18557
|
</html>`;
|
|
18295
18558
|
}
|
|
@@ -18536,6 +18799,10 @@ a:hover { text-decoration: underline; }
|
|
|
18536
18799
|
/* Table */
|
|
18537
18800
|
.table-wrap {
|
|
18538
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);
|
|
18539
18806
|
}
|
|
18540
18807
|
|
|
18541
18808
|
table {
|
|
@@ -18551,6 +18818,10 @@ th {
|
|
|
18551
18818
|
letter-spacing: 0.05em;
|
|
18552
18819
|
color: var(--text-dim);
|
|
18553
18820
|
border-bottom: 1px solid var(--border);
|
|
18821
|
+
position: sticky;
|
|
18822
|
+
top: 0;
|
|
18823
|
+
background: var(--bg-card);
|
|
18824
|
+
z-index: 1;
|
|
18554
18825
|
}
|
|
18555
18826
|
|
|
18556
18827
|
td {
|
|
@@ -18598,6 +18869,8 @@ tr:hover td {
|
|
|
18598
18869
|
border: 1px solid var(--border);
|
|
18599
18870
|
border-radius: var(--radius);
|
|
18600
18871
|
padding: 1.25rem;
|
|
18872
|
+
display: flex;
|
|
18873
|
+
flex-direction: column;
|
|
18601
18874
|
}
|
|
18602
18875
|
|
|
18603
18876
|
.gar-area .area-header {
|
|
@@ -18628,6 +18901,9 @@ tr:hover td {
|
|
|
18628
18901
|
.gar-area ul {
|
|
18629
18902
|
list-style: none;
|
|
18630
18903
|
font-size: 0.8rem;
|
|
18904
|
+
max-height: 200px;
|
|
18905
|
+
overflow-y: auto;
|
|
18906
|
+
scrollbar-width: thin;
|
|
18631
18907
|
}
|
|
18632
18908
|
|
|
18633
18909
|
.gar-area li {
|
|
@@ -18650,13 +18926,14 @@ tr:hover td {
|
|
|
18650
18926
|
display: flex;
|
|
18651
18927
|
gap: 1rem;
|
|
18652
18928
|
overflow-x: auto;
|
|
18929
|
+
scrollbar-width: thin;
|
|
18653
18930
|
padding-bottom: 1rem;
|
|
18654
18931
|
}
|
|
18655
18932
|
|
|
18656
18933
|
.board-column {
|
|
18657
18934
|
min-width: 240px;
|
|
18658
18935
|
max-width: 300px;
|
|
18659
|
-
flex:
|
|
18936
|
+
flex: 0 0 auto;
|
|
18660
18937
|
}
|
|
18661
18938
|
|
|
18662
18939
|
.board-column-header {
|
|
@@ -18669,6 +18946,7 @@ tr:hover td {
|
|
|
18669
18946
|
margin-bottom: 0.5rem;
|
|
18670
18947
|
display: flex;
|
|
18671
18948
|
justify-content: space-between;
|
|
18949
|
+
flex-shrink: 0;
|
|
18672
18950
|
}
|
|
18673
18951
|
|
|
18674
18952
|
.board-column-header .count {
|
|
@@ -18850,106 +19128,518 @@ tr:hover td {
|
|
|
18850
19128
|
.mermaid-row .mermaid-container {
|
|
18851
19129
|
margin: 0;
|
|
18852
19130
|
}
|
|
18853
|
-
`;
|
|
18854
|
-
}
|
|
18855
19131
|
|
|
18856
|
-
|
|
18857
|
-
|
|
18858
|
-
|
|
18859
|
-
|
|
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;
|
|
18860
19140
|
}
|
|
18861
|
-
|
|
18862
|
-
|
|
18863
|
-
|
|
18864
|
-
|
|
19141
|
+
|
|
19142
|
+
.flow-lines {
|
|
19143
|
+
position: absolute;
|
|
19144
|
+
top: 0;
|
|
19145
|
+
left: 0;
|
|
19146
|
+
pointer-events: none;
|
|
18865
19147
|
}
|
|
18866
|
-
|
|
18867
|
-
|
|
19148
|
+
|
|
19149
|
+
.flow-columns {
|
|
19150
|
+
display: flex;
|
|
19151
|
+
gap: 3rem;
|
|
19152
|
+
position: relative;
|
|
19153
|
+
min-width: 600px;
|
|
18868
19154
|
}
|
|
18869
|
-
|
|
18870
|
-
|
|
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
|
+
}
|
|
19366
|
+
`;
|
|
19367
|
+
}
|
|
19368
|
+
|
|
19369
|
+
// src/web/templates/mermaid.ts
|
|
19370
|
+
function sanitize(text, maxLen = 40) {
|
|
19371
|
+
const cleaned = text.replace(/["'`]/g, "").replace(/[\r\n]+/g, " ");
|
|
19372
|
+
return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 1) + "\u2026" : cleaned;
|
|
19373
|
+
}
|
|
19374
|
+
function mermaidBlock(definition, extraClass) {
|
|
19375
|
+
const cls = ["mermaid-container", extraClass].filter(Boolean).join(" ");
|
|
19376
|
+
return `<div class="${cls}"><pre class="mermaid">
|
|
19377
|
+
${definition}
|
|
19378
|
+
</pre></div>`;
|
|
19379
|
+
}
|
|
19380
|
+
function placeholder(message) {
|
|
19381
|
+
return `<div class="mermaid-container mermaid-empty"><p>${message}</p></div>`;
|
|
19382
|
+
}
|
|
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);
|
|
18871
19393
|
if (sprintsWithDates.length === 0) {
|
|
18872
19394
|
return placeholder("No timeline data available \u2014 sprints need start and end dates.");
|
|
18873
19395
|
}
|
|
19396
|
+
const truncated = sprintsWithDates.length > maxSprints;
|
|
19397
|
+
const visibleSprints = truncated ? sprintsWithDates.slice(-maxSprints) : sprintsWithDates;
|
|
19398
|
+
const hiddenCount = sprintsWithDates.length - visibleSprints.length;
|
|
18874
19399
|
const epicMap = new Map(data.epics.map((e) => [e.id, e]));
|
|
18875
|
-
const
|
|
18876
|
-
|
|
18877
|
-
|
|
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>`);
|
|
18878
19433
|
const linked = sprint.linkedEpics.map((eid) => epicMap.get(eid)).filter(Boolean);
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
|
|
18882
|
-
|
|
18883
|
-
|
|
18884
|
-
|
|
18885
|
-
|
|
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>`);
|
|
18886
19445
|
}
|
|
18887
19446
|
}
|
|
18888
|
-
|
|
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";
|
|
18889
19469
|
}
|
|
18890
19470
|
function buildArtifactFlowchart(data) {
|
|
18891
19471
|
if (data.features.length === 0 && data.epics.length === 0) {
|
|
18892
19472
|
return placeholder("No artifact relationships found \u2014 create features and epics to see the hierarchy.");
|
|
18893
19473
|
}
|
|
18894
|
-
const
|
|
18895
|
-
|
|
18896
|
-
lines.push(" classDef inprogress fill:#78350f,stroke:#fbbf24,color:#fef3c7");
|
|
18897
|
-
lines.push(" classDef blocked fill:#7f1d1d,stroke:#f87171,color:#fee2e2");
|
|
18898
|
-
lines.push(" classDef default fill:#1e293b,stroke:#475569,color:#e2e8f0");
|
|
18899
|
-
const nodeIds = /* @__PURE__ */ new Set();
|
|
19474
|
+
const edges = [];
|
|
19475
|
+
const epicsByFeature = /* @__PURE__ */ new Map();
|
|
18900
19476
|
for (const epic of data.epics) {
|
|
18901
|
-
for (const
|
|
18902
|
-
|
|
18903
|
-
|
|
18904
|
-
|
|
18905
|
-
const eNode = epic.id.replace(/-/g, "_");
|
|
18906
|
-
if (!nodeIds.has(fNode)) {
|
|
18907
|
-
lines.push(` ${fNode}["${sanitize(feature.id + " " + feature.title)}"]`);
|
|
18908
|
-
nodeIds.add(fNode);
|
|
18909
|
-
}
|
|
18910
|
-
if (!nodeIds.has(eNode)) {
|
|
18911
|
-
lines.push(` ${eNode}["${sanitize(epic.id + " " + epic.title)}"]`);
|
|
18912
|
-
nodeIds.add(eNode);
|
|
18913
|
-
}
|
|
18914
|
-
lines.push(` ${fNode} --> ${eNode}`);
|
|
18915
|
-
}
|
|
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 });
|
|
18916
19481
|
}
|
|
18917
19482
|
}
|
|
19483
|
+
const sprintsByEpic = /* @__PURE__ */ new Map();
|
|
18918
19484
|
for (const sprint of data.sprints) {
|
|
18919
|
-
const
|
|
18920
|
-
|
|
18921
|
-
|
|
18922
|
-
|
|
18923
|
-
|
|
18924
|
-
|
|
18925
|
-
|
|
18926
|
-
|
|
18927
|
-
|
|
18928
|
-
|
|
18929
|
-
|
|
18930
|
-
|
|
18931
|
-
|
|
18932
|
-
|
|
18933
|
-
|
|
18934
|
-
|
|
18935
|
-
|
|
18936
|
-
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) {
|
|
18937
19503
|
return placeholder("No artifact relationships found \u2014 link epics to features and sprints.");
|
|
18938
19504
|
}
|
|
18939
|
-
const
|
|
18940
|
-
|
|
18941
|
-
|
|
18942
|
-
|
|
18943
|
-
|
|
18944
|
-
|
|
18945
|
-
|
|
18946
|
-
|
|
18947
|
-
|
|
18948
|
-
|
|
18949
|
-
lines
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
|
|
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>`;
|
|
18953
19643
|
}
|
|
18954
19644
|
function buildStatusPie(title, counts) {
|
|
18955
19645
|
const entries = Object.entries(counts).filter(([, v]) => v > 0);
|
|
@@ -19029,8 +19719,7 @@ function overviewPage(data, diagrams, navGroups) {
|
|
|
19029
19719
|
${groupSections}
|
|
19030
19720
|
${ungroupedSection}
|
|
19031
19721
|
|
|
19032
|
-
<div class="section-title">Project Timeline
|
|
19033
|
-
${buildTimelineGantt(diagrams)}
|
|
19722
|
+
<div class="section-title"><a href="/timeline">Project Timeline →</a></div>
|
|
19034
19723
|
|
|
19035
19724
|
<div class="section-title">Artifact Relationships</div>
|
|
19036
19725
|
${buildArtifactFlowchart(diagrams)}
|
|
@@ -19285,6 +19974,7 @@ function boardPage(data) {
|
|
|
19285
19974
|
<span>${escapeHtml(col.status)}</span>
|
|
19286
19975
|
<span class="count">${col.docs.length}</span>
|
|
19287
19976
|
</div>
|
|
19977
|
+
<div class="board-column-cards">
|
|
19288
19978
|
${col.docs.map(
|
|
19289
19979
|
(doc) => `
|
|
19290
19980
|
<div class="board-card">
|
|
@@ -19295,6 +19985,7 @@ function boardPage(data) {
|
|
|
19295
19985
|
</a>
|
|
19296
19986
|
</div>`
|
|
19297
19987
|
).join("\n")}
|
|
19988
|
+
</div>
|
|
19298
19989
|
</div>`
|
|
19299
19990
|
).join("\n");
|
|
19300
19991
|
return `
|
|
@@ -19320,6 +20011,18 @@ function boardPage(data) {
|
|
|
19320
20011
|
`;
|
|
19321
20012
|
}
|
|
19322
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
|
+
|
|
19323
20026
|
// src/web/router.ts
|
|
19324
20027
|
function handleRequest(req, res, store, projectName, navGroups) {
|
|
19325
20028
|
const parsed = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
@@ -19341,6 +20044,12 @@ function handleRequest(req, res, store, projectName, navGroups) {
|
|
|
19341
20044
|
respond(res, layout({ title: "Overview", activePath: "/", projectName, navGroups }, body));
|
|
19342
20045
|
return;
|
|
19343
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
|
+
}
|
|
19344
20053
|
if (pathname === "/gar") {
|
|
19345
20054
|
const report = getGarData(store, projectName);
|
|
19346
20055
|
const body = garPage(report);
|
|
@@ -19414,8 +20123,8 @@ import * as http from "http";
|
|
|
19414
20123
|
import { exec } from "child_process";
|
|
19415
20124
|
|
|
19416
20125
|
// src/skills/registry.ts
|
|
19417
|
-
import * as
|
|
19418
|
-
import * as
|
|
20126
|
+
import * as fs9 from "fs";
|
|
20127
|
+
import * as path9 from "path";
|
|
19419
20128
|
import { fileURLToPath } from "url";
|
|
19420
20129
|
import * as YAML5 from "yaml";
|
|
19421
20130
|
import matter2 from "gray-matter";
|
|
@@ -19458,7 +20167,7 @@ Be thorough but concise. Focus on actionable insights.`,
|
|
|
19458
20167
|
};
|
|
19459
20168
|
|
|
19460
20169
|
// src/skills/builtin/jira/tools.ts
|
|
19461
|
-
import { tool as
|
|
20170
|
+
import { tool as tool20 } from "@anthropic-ai/claude-agent-sdk";
|
|
19462
20171
|
|
|
19463
20172
|
// src/skills/builtin/jira/client.ts
|
|
19464
20173
|
var JiraClient = class {
|
|
@@ -19468,8 +20177,8 @@ var JiraClient = class {
|
|
|
19468
20177
|
this.baseUrl = `https://${config2.host}/rest/api/2`;
|
|
19469
20178
|
this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
|
|
19470
20179
|
}
|
|
19471
|
-
async request(
|
|
19472
|
-
const url2 = `${this.baseUrl}${
|
|
20180
|
+
async request(path21, method = "GET", body) {
|
|
20181
|
+
const url2 = `${this.baseUrl}${path21}`;
|
|
19473
20182
|
const headers = {
|
|
19474
20183
|
Authorization: this.authHeader,
|
|
19475
20184
|
"Content-Type": "application/json",
|
|
@@ -19483,7 +20192,7 @@ var JiraClient = class {
|
|
|
19483
20192
|
if (!response.ok) {
|
|
19484
20193
|
const text = await response.text().catch(() => "");
|
|
19485
20194
|
throw new Error(
|
|
19486
|
-
`Jira API error ${response.status} ${method} ${
|
|
20195
|
+
`Jira API error ${response.status} ${method} ${path21}: ${text}`
|
|
19487
20196
|
);
|
|
19488
20197
|
}
|
|
19489
20198
|
if (response.status === 204) return void 0;
|
|
@@ -19567,7 +20276,7 @@ function createJiraTools(store) {
|
|
|
19567
20276
|
const jiraUserConfig = loadUserConfig().jira;
|
|
19568
20277
|
return [
|
|
19569
20278
|
// --- Local read tools ---
|
|
19570
|
-
|
|
20279
|
+
tool20(
|
|
19571
20280
|
"list_jira_issues",
|
|
19572
20281
|
"List locally synced Jira issues (JI-xxx documents), optionally filtered by status or Jira key",
|
|
19573
20282
|
{
|
|
@@ -19595,7 +20304,7 @@ function createJiraTools(store) {
|
|
|
19595
20304
|
},
|
|
19596
20305
|
{ annotations: { readOnlyHint: true } }
|
|
19597
20306
|
),
|
|
19598
|
-
|
|
20307
|
+
tool20(
|
|
19599
20308
|
"get_jira_issue",
|
|
19600
20309
|
"Get the full content of a locally synced Jira issue by local ID (JI-xxx) or Jira key (PROJ-123)",
|
|
19601
20310
|
{
|
|
@@ -19628,7 +20337,7 @@ function createJiraTools(store) {
|
|
|
19628
20337
|
{ annotations: { readOnlyHint: true } }
|
|
19629
20338
|
),
|
|
19630
20339
|
// --- Jira → Local tools ---
|
|
19631
|
-
|
|
20340
|
+
tool20(
|
|
19632
20341
|
"pull_jira_issue",
|
|
19633
20342
|
"Fetch a single Jira issue by key and create/update a local JI-xxx document",
|
|
19634
20343
|
{
|
|
@@ -19675,7 +20384,7 @@ function createJiraTools(store) {
|
|
|
19675
20384
|
};
|
|
19676
20385
|
}
|
|
19677
20386
|
),
|
|
19678
|
-
|
|
20387
|
+
tool20(
|
|
19679
20388
|
"pull_jira_issues_jql",
|
|
19680
20389
|
"Bulk fetch Jira issues via JQL query and create/update local JI-xxx documents",
|
|
19681
20390
|
{
|
|
@@ -19723,7 +20432,7 @@ function createJiraTools(store) {
|
|
|
19723
20432
|
}
|
|
19724
20433
|
),
|
|
19725
20434
|
// --- Local → Jira tools ---
|
|
19726
|
-
|
|
20435
|
+
tool20(
|
|
19727
20436
|
"push_artifact_to_jira",
|
|
19728
20437
|
"Create a Jira issue from any Marvin artifact (D/A/Q/F/E) and create a tracking JI-xxx document",
|
|
19729
20438
|
{
|
|
@@ -19784,7 +20493,7 @@ function createJiraTools(store) {
|
|
|
19784
20493
|
}
|
|
19785
20494
|
),
|
|
19786
20495
|
// --- Bidirectional sync ---
|
|
19787
|
-
|
|
20496
|
+
tool20(
|
|
19788
20497
|
"sync_jira_issue",
|
|
19789
20498
|
"Bidirectional sync: push local title/description to Jira, pull latest status/assignee/labels back",
|
|
19790
20499
|
{
|
|
@@ -19825,7 +20534,7 @@ function createJiraTools(store) {
|
|
|
19825
20534
|
}
|
|
19826
20535
|
),
|
|
19827
20536
|
// --- Local link tool ---
|
|
19828
|
-
|
|
20537
|
+
tool20(
|
|
19829
20538
|
"link_artifact_to_jira",
|
|
19830
20539
|
"Add a Marvin artifact ID to a JI-xxx document's linkedArtifacts field",
|
|
19831
20540
|
{
|
|
@@ -19913,13 +20622,13 @@ var jiraSkill = {
|
|
|
19913
20622
|
**Available tools:**
|
|
19914
20623
|
- \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues
|
|
19915
20624
|
- \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
|
|
19916
|
-
- \`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.)
|
|
19917
20626
|
- \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
|
|
19918
20627
|
- \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx
|
|
19919
20628
|
|
|
19920
20629
|
**As Tech Lead, use Jira integration to:**
|
|
19921
20630
|
- Pull technical issues and bugs for sprint planning and estimation
|
|
19922
|
-
- Push epics and technical decisions to Jira for cross-team visibility
|
|
20631
|
+
- Push epics, tasks, and technical decisions to Jira for cross-team visibility
|
|
19923
20632
|
- Bidirectional sync to keep local governance and Jira in alignment
|
|
19924
20633
|
- Use JQL queries to track technical debt (e.g. \`labels = "tech-debt" AND status != "Done"\`)`,
|
|
19925
20634
|
"delivery-manager": `You have the **Jira Integration** skill. You can pull issues from Jira and push Marvin artifacts to Jira.
|
|
@@ -19933,16 +20642,420 @@ var jiraSkill = {
|
|
|
19933
20642
|
|
|
19934
20643
|
**As Delivery Manager, use Jira integration to:**
|
|
19935
20644
|
- Pull sprint issues for tracking progress and blockers
|
|
19936
|
-
- Push actions and
|
|
20645
|
+
- Push actions, decisions, and tasks to Jira for stakeholder visibility
|
|
19937
20646
|
- Use JQL queries for reporting (e.g. \`sprint in openSprints() AND assignee = currentUser()\`)
|
|
19938
20647
|
- Sync status between Marvin governance items and Jira issues`
|
|
19939
20648
|
}
|
|
19940
20649
|
};
|
|
19941
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
|
+
|
|
19942
21054
|
// src/skills/registry.ts
|
|
19943
21055
|
var BUILTIN_SKILLS = {
|
|
19944
21056
|
"governance-review": governanceReviewSkill,
|
|
19945
|
-
"jira": jiraSkill
|
|
21057
|
+
"jira": jiraSkill,
|
|
21058
|
+
"prd-generator": prdGeneratorSkill
|
|
19946
21059
|
};
|
|
19947
21060
|
var GOVERNANCE_TOOL_NAMES = [
|
|
19948
21061
|
"mcp__marvin-governance__list_decisions",
|
|
@@ -19963,13 +21076,13 @@ var GOVERNANCE_TOOL_NAMES = [
|
|
|
19963
21076
|
];
|
|
19964
21077
|
function getBuiltinSkillsDir() {
|
|
19965
21078
|
const thisFile = fileURLToPath(import.meta.url);
|
|
19966
|
-
return
|
|
21079
|
+
return path9.join(path9.dirname(thisFile), "builtin");
|
|
19967
21080
|
}
|
|
19968
21081
|
function loadSkillFromDirectory(dirPath) {
|
|
19969
|
-
const skillMdPath =
|
|
19970
|
-
if (!
|
|
21082
|
+
const skillMdPath = path9.join(dirPath, "SKILL.md");
|
|
21083
|
+
if (!fs9.existsSync(skillMdPath)) return void 0;
|
|
19971
21084
|
try {
|
|
19972
|
-
const raw =
|
|
21085
|
+
const raw = fs9.readFileSync(skillMdPath, "utf-8");
|
|
19973
21086
|
const { data, content } = matter2(raw);
|
|
19974
21087
|
if (!data.name || !data.description) return void 0;
|
|
19975
21088
|
const metadata = data.metadata ?? {};
|
|
@@ -19980,13 +21093,13 @@ function loadSkillFromDirectory(dirPath) {
|
|
|
19980
21093
|
if (wildcardPrompt) {
|
|
19981
21094
|
promptFragments["*"] = wildcardPrompt;
|
|
19982
21095
|
}
|
|
19983
|
-
const personasDir =
|
|
19984
|
-
if (
|
|
21096
|
+
const personasDir = path9.join(dirPath, "personas");
|
|
21097
|
+
if (fs9.existsSync(personasDir)) {
|
|
19985
21098
|
try {
|
|
19986
|
-
for (const file2 of
|
|
21099
|
+
for (const file2 of fs9.readdirSync(personasDir)) {
|
|
19987
21100
|
if (!file2.endsWith(".md")) continue;
|
|
19988
21101
|
const personaId = file2.replace(/\.md$/, "");
|
|
19989
|
-
const personaPrompt =
|
|
21102
|
+
const personaPrompt = fs9.readFileSync(path9.join(personasDir, file2), "utf-8").trim();
|
|
19990
21103
|
if (personaPrompt) {
|
|
19991
21104
|
promptFragments[personaId] = personaPrompt;
|
|
19992
21105
|
}
|
|
@@ -19995,10 +21108,10 @@ function loadSkillFromDirectory(dirPath) {
|
|
|
19995
21108
|
}
|
|
19996
21109
|
}
|
|
19997
21110
|
let actions;
|
|
19998
|
-
const actionsPath =
|
|
19999
|
-
if (
|
|
21111
|
+
const actionsPath = path9.join(dirPath, "actions.yaml");
|
|
21112
|
+
if (fs9.existsSync(actionsPath)) {
|
|
20000
21113
|
try {
|
|
20001
|
-
const actionsRaw =
|
|
21114
|
+
const actionsRaw = fs9.readFileSync(actionsPath, "utf-8");
|
|
20002
21115
|
actions = YAML5.parse(actionsRaw);
|
|
20003
21116
|
} catch {
|
|
20004
21117
|
}
|
|
@@ -20025,10 +21138,10 @@ function loadAllSkills(marvinDir) {
|
|
|
20025
21138
|
}
|
|
20026
21139
|
try {
|
|
20027
21140
|
const builtinDir = getBuiltinSkillsDir();
|
|
20028
|
-
if (
|
|
20029
|
-
for (const entry of
|
|
20030
|
-
const entryPath =
|
|
20031
|
-
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;
|
|
20032
21145
|
if (skills.has(entry)) continue;
|
|
20033
21146
|
const skill = loadSkillFromDirectory(entryPath);
|
|
20034
21147
|
if (skill) skills.set(skill.id, skill);
|
|
@@ -20037,18 +21150,18 @@ function loadAllSkills(marvinDir) {
|
|
|
20037
21150
|
} catch {
|
|
20038
21151
|
}
|
|
20039
21152
|
if (marvinDir) {
|
|
20040
|
-
const skillsDir =
|
|
20041
|
-
if (
|
|
21153
|
+
const skillsDir = path9.join(marvinDir, "skills");
|
|
21154
|
+
if (fs9.existsSync(skillsDir)) {
|
|
20042
21155
|
let entries;
|
|
20043
21156
|
try {
|
|
20044
|
-
entries =
|
|
21157
|
+
entries = fs9.readdirSync(skillsDir);
|
|
20045
21158
|
} catch {
|
|
20046
21159
|
entries = [];
|
|
20047
21160
|
}
|
|
20048
21161
|
for (const entry of entries) {
|
|
20049
|
-
const entryPath =
|
|
21162
|
+
const entryPath = path9.join(skillsDir, entry);
|
|
20050
21163
|
try {
|
|
20051
|
-
if (
|
|
21164
|
+
if (fs9.statSync(entryPath).isDirectory()) {
|
|
20052
21165
|
const skill = loadSkillFromDirectory(entryPath);
|
|
20053
21166
|
if (skill) skills.set(skill.id, skill);
|
|
20054
21167
|
continue;
|
|
@@ -20058,7 +21171,7 @@ function loadAllSkills(marvinDir) {
|
|
|
20058
21171
|
}
|
|
20059
21172
|
if (!entry.endsWith(".yaml") && !entry.endsWith(".yml")) continue;
|
|
20060
21173
|
try {
|
|
20061
|
-
const raw =
|
|
21174
|
+
const raw = fs9.readFileSync(entryPath, "utf-8");
|
|
20062
21175
|
const parsed = YAML5.parse(raw);
|
|
20063
21176
|
if (!parsed?.id || !parsed?.name || !parsed?.version) continue;
|
|
20064
21177
|
const skill = {
|
|
@@ -20163,12 +21276,12 @@ function getSkillAgentDefinitions(skillIds, allSkills) {
|
|
|
20163
21276
|
return agents;
|
|
20164
21277
|
}
|
|
20165
21278
|
function migrateYamlToSkillMd(yamlPath, outputDir) {
|
|
20166
|
-
const raw =
|
|
21279
|
+
const raw = fs9.readFileSync(yamlPath, "utf-8");
|
|
20167
21280
|
const parsed = YAML5.parse(raw);
|
|
20168
21281
|
if (!parsed?.id || !parsed?.name) {
|
|
20169
21282
|
throw new Error(`Invalid skill YAML: missing required fields (id, name)`);
|
|
20170
21283
|
}
|
|
20171
|
-
|
|
21284
|
+
fs9.mkdirSync(outputDir, { recursive: true });
|
|
20172
21285
|
const frontmatter = {
|
|
20173
21286
|
name: parsed.id,
|
|
20174
21287
|
description: parsed.description ?? ""
|
|
@@ -20182,15 +21295,15 @@ function migrateYamlToSkillMd(yamlPath, outputDir) {
|
|
|
20182
21295
|
const skillMd = matter2.stringify(wildcardPrompt ? `
|
|
20183
21296
|
${wildcardPrompt}
|
|
20184
21297
|
` : "\n", frontmatter);
|
|
20185
|
-
|
|
21298
|
+
fs9.writeFileSync(path9.join(outputDir, "SKILL.md"), skillMd, "utf-8");
|
|
20186
21299
|
if (promptFragments) {
|
|
20187
21300
|
const personaKeys = Object.keys(promptFragments).filter((k) => k !== "*");
|
|
20188
21301
|
if (personaKeys.length > 0) {
|
|
20189
|
-
const personasDir =
|
|
20190
|
-
|
|
21302
|
+
const personasDir = path9.join(outputDir, "personas");
|
|
21303
|
+
fs9.mkdirSync(personasDir, { recursive: true });
|
|
20191
21304
|
for (const personaId of personaKeys) {
|
|
20192
|
-
|
|
20193
|
-
|
|
21305
|
+
fs9.writeFileSync(
|
|
21306
|
+
path9.join(personasDir, `${personaId}.md`),
|
|
20194
21307
|
`${promptFragments[personaId]}
|
|
20195
21308
|
`,
|
|
20196
21309
|
"utf-8"
|
|
@@ -20200,8 +21313,8 @@ ${wildcardPrompt}
|
|
|
20200
21313
|
}
|
|
20201
21314
|
const actions = parsed.actions;
|
|
20202
21315
|
if (actions && actions.length > 0) {
|
|
20203
|
-
|
|
20204
|
-
|
|
21316
|
+
fs9.writeFileSync(
|
|
21317
|
+
path9.join(outputDir, "actions.yaml"),
|
|
20205
21318
|
YAML5.stringify(actions),
|
|
20206
21319
|
"utf-8"
|
|
20207
21320
|
);
|
|
@@ -20280,7 +21393,7 @@ function openBrowser(url2) {
|
|
|
20280
21393
|
var runningServer = null;
|
|
20281
21394
|
function createWebTools(store, projectName, navGroups) {
|
|
20282
21395
|
return [
|
|
20283
|
-
|
|
21396
|
+
tool22(
|
|
20284
21397
|
"start_web_dashboard",
|
|
20285
21398
|
"Start the Marvin web dashboard on a local port. Returns the base URL. If already running, returns the existing URL.",
|
|
20286
21399
|
{
|
|
@@ -20312,7 +21425,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20312
21425
|
};
|
|
20313
21426
|
}
|
|
20314
21427
|
),
|
|
20315
|
-
|
|
21428
|
+
tool22(
|
|
20316
21429
|
"stop_web_dashboard",
|
|
20317
21430
|
"Stop the running Marvin web dashboard.",
|
|
20318
21431
|
{},
|
|
@@ -20332,7 +21445,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20332
21445
|
};
|
|
20333
21446
|
}
|
|
20334
21447
|
),
|
|
20335
|
-
|
|
21448
|
+
tool22(
|
|
20336
21449
|
"get_web_dashboard_urls",
|
|
20337
21450
|
"Get all available dashboard page URLs. The dashboard must be running.",
|
|
20338
21451
|
{},
|
|
@@ -20358,7 +21471,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20358
21471
|
},
|
|
20359
21472
|
{ annotations: { readOnlyHint: true } }
|
|
20360
21473
|
),
|
|
20361
|
-
|
|
21474
|
+
tool22(
|
|
20362
21475
|
"get_dashboard_overview",
|
|
20363
21476
|
"Get the project overview data: document type counts and recent activity. Works without the web server running.",
|
|
20364
21477
|
{},
|
|
@@ -20380,7 +21493,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20380
21493
|
},
|
|
20381
21494
|
{ annotations: { readOnlyHint: true } }
|
|
20382
21495
|
),
|
|
20383
|
-
|
|
21496
|
+
tool22(
|
|
20384
21497
|
"get_dashboard_gar",
|
|
20385
21498
|
"Get the GAR (Governance, Actions, Risks) report as JSON. Works without the web server running.",
|
|
20386
21499
|
{},
|
|
@@ -20392,7 +21505,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
20392
21505
|
},
|
|
20393
21506
|
{ annotations: { readOnlyHint: true } }
|
|
20394
21507
|
),
|
|
20395
|
-
|
|
21508
|
+
tool22(
|
|
20396
21509
|
"get_dashboard_board",
|
|
20397
21510
|
"Get board data showing documents grouped by status. Optionally filter by document type. Works without the web server running.",
|
|
20398
21511
|
{
|
|
@@ -20475,8 +21588,8 @@ function slugify3(text) {
|
|
|
20475
21588
|
}
|
|
20476
21589
|
|
|
20477
21590
|
// src/sources/manifest.ts
|
|
20478
|
-
import * as
|
|
20479
|
-
import * as
|
|
21591
|
+
import * as fs10 from "fs";
|
|
21592
|
+
import * as path10 from "path";
|
|
20480
21593
|
import * as crypto from "crypto";
|
|
20481
21594
|
import * as YAML6 from "yaml";
|
|
20482
21595
|
var MANIFEST_FILE = ".manifest.yaml";
|
|
@@ -20489,37 +21602,37 @@ var SourceManifestManager = class {
|
|
|
20489
21602
|
manifestPath;
|
|
20490
21603
|
sourcesDir;
|
|
20491
21604
|
constructor(marvinDir) {
|
|
20492
|
-
this.sourcesDir =
|
|
20493
|
-
this.manifestPath =
|
|
21605
|
+
this.sourcesDir = path10.join(marvinDir, "sources");
|
|
21606
|
+
this.manifestPath = path10.join(this.sourcesDir, MANIFEST_FILE);
|
|
20494
21607
|
this.manifest = this.load();
|
|
20495
21608
|
}
|
|
20496
21609
|
load() {
|
|
20497
|
-
if (!
|
|
21610
|
+
if (!fs10.existsSync(this.manifestPath)) {
|
|
20498
21611
|
return emptyManifest();
|
|
20499
21612
|
}
|
|
20500
|
-
const raw =
|
|
21613
|
+
const raw = fs10.readFileSync(this.manifestPath, "utf-8");
|
|
20501
21614
|
const parsed = YAML6.parse(raw);
|
|
20502
21615
|
return parsed ?? emptyManifest();
|
|
20503
21616
|
}
|
|
20504
21617
|
save() {
|
|
20505
|
-
|
|
20506
|
-
|
|
21618
|
+
fs10.mkdirSync(this.sourcesDir, { recursive: true });
|
|
21619
|
+
fs10.writeFileSync(this.manifestPath, YAML6.stringify(this.manifest), "utf-8");
|
|
20507
21620
|
}
|
|
20508
21621
|
scan() {
|
|
20509
21622
|
const added = [];
|
|
20510
21623
|
const changed = [];
|
|
20511
21624
|
const removed = [];
|
|
20512
|
-
if (!
|
|
21625
|
+
if (!fs10.existsSync(this.sourcesDir)) {
|
|
20513
21626
|
return { added, changed, removed };
|
|
20514
21627
|
}
|
|
20515
21628
|
const onDisk = new Set(
|
|
20516
|
-
|
|
20517
|
-
const ext =
|
|
21629
|
+
fs10.readdirSync(this.sourcesDir).filter((f) => {
|
|
21630
|
+
const ext = path10.extname(f).toLowerCase();
|
|
20518
21631
|
return SOURCE_EXTENSIONS.includes(ext);
|
|
20519
21632
|
})
|
|
20520
21633
|
);
|
|
20521
21634
|
for (const fileName of onDisk) {
|
|
20522
|
-
const filePath =
|
|
21635
|
+
const filePath = path10.join(this.sourcesDir, fileName);
|
|
20523
21636
|
const hash2 = this.hashFile(filePath);
|
|
20524
21637
|
const existing = this.manifest.files[fileName];
|
|
20525
21638
|
if (!existing) {
|
|
@@ -20582,7 +21695,7 @@ var SourceManifestManager = class {
|
|
|
20582
21695
|
this.save();
|
|
20583
21696
|
}
|
|
20584
21697
|
hashFile(filePath) {
|
|
20585
|
-
const content =
|
|
21698
|
+
const content = fs10.readFileSync(filePath);
|
|
20586
21699
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
20587
21700
|
}
|
|
20588
21701
|
};
|
|
@@ -20597,8 +21710,8 @@ async function startSession(options) {
|
|
|
20597
21710
|
const skillRegistrations = collectSkillRegistrations(skillIds, allSkills);
|
|
20598
21711
|
const store = new DocumentStore(marvinDir, [...pluginRegistrations, ...skillRegistrations]);
|
|
20599
21712
|
const sessionStore = new SessionStore(marvinDir);
|
|
20600
|
-
const sourcesDir =
|
|
20601
|
-
const hasSourcesDir =
|
|
21713
|
+
const sourcesDir = path11.join(marvinDir, "sources");
|
|
21714
|
+
const hasSourcesDir = fs11.existsSync(sourcesDir);
|
|
20602
21715
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
20603
21716
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
20604
21717
|
const pluginPromptFragment = plugin ? getPluginPromptFragment(plugin, persona.id) : void 0;
|
|
@@ -21074,13 +22187,13 @@ async function setApiKey() {
|
|
|
21074
22187
|
}
|
|
21075
22188
|
|
|
21076
22189
|
// src/cli/commands/ingest.ts
|
|
21077
|
-
import * as
|
|
21078
|
-
import * as
|
|
22190
|
+
import * as fs13 from "fs";
|
|
22191
|
+
import * as path13 from "path";
|
|
21079
22192
|
import chalk8 from "chalk";
|
|
21080
22193
|
|
|
21081
22194
|
// src/sources/ingest.ts
|
|
21082
|
-
import * as
|
|
21083
|
-
import * as
|
|
22195
|
+
import * as fs12 from "fs";
|
|
22196
|
+
import * as path12 from "path";
|
|
21084
22197
|
import chalk7 from "chalk";
|
|
21085
22198
|
import ora2 from "ora";
|
|
21086
22199
|
import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -21183,15 +22296,15 @@ async function ingestFile(options) {
|
|
|
21183
22296
|
const persona = getPersona(personaId);
|
|
21184
22297
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21185
22298
|
const sourcesDir = manifest.sourcesDir;
|
|
21186
|
-
const filePath =
|
|
21187
|
-
if (!
|
|
22299
|
+
const filePath = path12.join(sourcesDir, fileName);
|
|
22300
|
+
if (!fs12.existsSync(filePath)) {
|
|
21188
22301
|
throw new Error(`Source file not found: ${filePath}`);
|
|
21189
22302
|
}
|
|
21190
|
-
const ext =
|
|
22303
|
+
const ext = path12.extname(fileName).toLowerCase();
|
|
21191
22304
|
const isPdf = ext === ".pdf";
|
|
21192
22305
|
let fileContent = null;
|
|
21193
22306
|
if (!isPdf) {
|
|
21194
|
-
fileContent =
|
|
22307
|
+
fileContent = fs12.readFileSync(filePath, "utf-8");
|
|
21195
22308
|
}
|
|
21196
22309
|
const store = new DocumentStore(marvinDir);
|
|
21197
22310
|
const createdArtifacts = [];
|
|
@@ -21294,9 +22407,9 @@ Ingest ended with error: ${message.subtype}`)
|
|
|
21294
22407
|
async function ingestCommand(file2, options) {
|
|
21295
22408
|
const project = loadProject();
|
|
21296
22409
|
const marvinDir = project.marvinDir;
|
|
21297
|
-
const sourcesDir =
|
|
21298
|
-
if (!
|
|
21299
|
-
|
|
22410
|
+
const sourcesDir = path13.join(marvinDir, "sources");
|
|
22411
|
+
if (!fs13.existsSync(sourcesDir)) {
|
|
22412
|
+
fs13.mkdirSync(sourcesDir, { recursive: true });
|
|
21300
22413
|
}
|
|
21301
22414
|
const manifest = new SourceManifestManager(marvinDir);
|
|
21302
22415
|
manifest.scan();
|
|
@@ -21307,8 +22420,8 @@ async function ingestCommand(file2, options) {
|
|
|
21307
22420
|
return;
|
|
21308
22421
|
}
|
|
21309
22422
|
if (file2) {
|
|
21310
|
-
const filePath =
|
|
21311
|
-
if (!
|
|
22423
|
+
const filePath = path13.join(sourcesDir, file2);
|
|
22424
|
+
if (!fs13.existsSync(filePath)) {
|
|
21312
22425
|
console.log(chalk8.red(`Source file not found: ${file2}`));
|
|
21313
22426
|
console.log(chalk8.dim(`Expected at: ${filePath}`));
|
|
21314
22427
|
console.log(chalk8.dim(`Drop files into .marvin/sources/ and try again.`));
|
|
@@ -21375,7 +22488,7 @@ import ora3 from "ora";
|
|
|
21375
22488
|
import { input as input3 } from "@inquirer/prompts";
|
|
21376
22489
|
|
|
21377
22490
|
// src/git/repository.ts
|
|
21378
|
-
import * as
|
|
22491
|
+
import * as path14 from "path";
|
|
21379
22492
|
import simpleGit from "simple-git";
|
|
21380
22493
|
var MARVIN_GITIGNORE = `node_modules/
|
|
21381
22494
|
.DS_Store
|
|
@@ -21395,7 +22508,7 @@ var DIR_TYPE_LABELS = {
|
|
|
21395
22508
|
function buildCommitMessage(files) {
|
|
21396
22509
|
const counts = /* @__PURE__ */ new Map();
|
|
21397
22510
|
for (const f of files) {
|
|
21398
|
-
const parts2 = f.split(
|
|
22511
|
+
const parts2 = f.split(path14.sep).join("/").split("/");
|
|
21399
22512
|
const docsIdx = parts2.indexOf("docs");
|
|
21400
22513
|
if (docsIdx !== -1 && docsIdx + 1 < parts2.length) {
|
|
21401
22514
|
const dirName = parts2[docsIdx + 1];
|
|
@@ -21435,9 +22548,9 @@ var MarvinGit = class {
|
|
|
21435
22548
|
);
|
|
21436
22549
|
}
|
|
21437
22550
|
await this.git.init();
|
|
21438
|
-
const { writeFileSync:
|
|
21439
|
-
|
|
21440
|
-
|
|
22551
|
+
const { writeFileSync: writeFileSync11 } = await import("fs");
|
|
22552
|
+
writeFileSync11(
|
|
22553
|
+
path14.join(this.marvinDir, ".gitignore"),
|
|
21441
22554
|
MARVIN_GITIGNORE,
|
|
21442
22555
|
"utf-8"
|
|
21443
22556
|
);
|
|
@@ -21557,7 +22670,7 @@ var MarvinGit = class {
|
|
|
21557
22670
|
}
|
|
21558
22671
|
}
|
|
21559
22672
|
static async clone(url2, targetDir) {
|
|
21560
|
-
const marvinDir =
|
|
22673
|
+
const marvinDir = path14.join(targetDir, ".marvin");
|
|
21561
22674
|
const { existsSync: existsSync17 } = await import("fs");
|
|
21562
22675
|
if (existsSync17(marvinDir)) {
|
|
21563
22676
|
throw new GitSyncError(
|
|
@@ -21737,13 +22850,13 @@ async function cloneCommand(url2, directory) {
|
|
|
21737
22850
|
}
|
|
21738
22851
|
|
|
21739
22852
|
// src/mcp/stdio-server.ts
|
|
21740
|
-
import * as
|
|
21741
|
-
import * as
|
|
22853
|
+
import * as fs14 from "fs";
|
|
22854
|
+
import * as path15 from "path";
|
|
21742
22855
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21743
22856
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
21744
22857
|
|
|
21745
22858
|
// src/skills/action-tools.ts
|
|
21746
|
-
import { tool as
|
|
22859
|
+
import { tool as tool23 } from "@anthropic-ai/claude-agent-sdk";
|
|
21747
22860
|
|
|
21748
22861
|
// src/skills/action-runner.ts
|
|
21749
22862
|
import { query as query4 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -21809,7 +22922,7 @@ function createSkillActionTools(skills, context) {
|
|
|
21809
22922
|
if (!skill.actions) continue;
|
|
21810
22923
|
for (const action of skill.actions) {
|
|
21811
22924
|
tools.push(
|
|
21812
|
-
|
|
22925
|
+
tool23(
|
|
21813
22926
|
`${skill.id}__${action.id}`,
|
|
21814
22927
|
action.description,
|
|
21815
22928
|
{
|
|
@@ -21901,10 +23014,10 @@ ${lines.join("\n\n")}`;
|
|
|
21901
23014
|
}
|
|
21902
23015
|
|
|
21903
23016
|
// src/mcp/persona-tools.ts
|
|
21904
|
-
import { tool as
|
|
23017
|
+
import { tool as tool24 } from "@anthropic-ai/claude-agent-sdk";
|
|
21905
23018
|
function createPersonaTools(ctx, marvinDir) {
|
|
21906
23019
|
return [
|
|
21907
|
-
|
|
23020
|
+
tool24(
|
|
21908
23021
|
"set_persona",
|
|
21909
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.",
|
|
21910
23023
|
{
|
|
@@ -21934,7 +23047,7 @@ ${summaries}`
|
|
|
21934
23047
|
};
|
|
21935
23048
|
}
|
|
21936
23049
|
),
|
|
21937
|
-
|
|
23050
|
+
tool24(
|
|
21938
23051
|
"get_persona_guidance",
|
|
21939
23052
|
"Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
|
|
21940
23053
|
{
|
|
@@ -22036,8 +23149,8 @@ function collectTools(marvinDir) {
|
|
|
22036
23149
|
const plugin = resolvePlugin(config2.methodology);
|
|
22037
23150
|
const registrations = plugin?.documentTypeRegistrations ?? [];
|
|
22038
23151
|
const store = new DocumentStore(marvinDir, registrations);
|
|
22039
|
-
const sourcesDir =
|
|
22040
|
-
const hasSourcesDir =
|
|
23152
|
+
const sourcesDir = path15.join(marvinDir, "sources");
|
|
23153
|
+
const hasSourcesDir = fs14.existsSync(sourcesDir);
|
|
22041
23154
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
22042
23155
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
22043
23156
|
const sessionStore = new SessionStore(marvinDir);
|
|
@@ -22045,7 +23158,7 @@ function collectTools(marvinDir) {
|
|
|
22045
23158
|
const allSkillIds = [...allSkills.keys()];
|
|
22046
23159
|
const codeSkillTools = getSkillTools(allSkillIds, allSkills, store);
|
|
22047
23160
|
const skillsWithActions = allSkillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
|
|
22048
|
-
const projectRoot =
|
|
23161
|
+
const projectRoot = path15.dirname(marvinDir);
|
|
22049
23162
|
const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
|
|
22050
23163
|
const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
|
|
22051
23164
|
const navGroups = buildNavGroups({
|
|
@@ -22115,8 +23228,8 @@ async function serveCommand() {
|
|
|
22115
23228
|
}
|
|
22116
23229
|
|
|
22117
23230
|
// src/cli/commands/skills.ts
|
|
22118
|
-
import * as
|
|
22119
|
-
import * as
|
|
23231
|
+
import * as fs15 from "fs";
|
|
23232
|
+
import * as path16 from "path";
|
|
22120
23233
|
import * as YAML7 from "yaml";
|
|
22121
23234
|
import matter3 from "gray-matter";
|
|
22122
23235
|
import chalk10 from "chalk";
|
|
@@ -22222,14 +23335,14 @@ async function skillsRemoveCommand(skillId, options) {
|
|
|
22222
23335
|
}
|
|
22223
23336
|
async function skillsCreateCommand(name) {
|
|
22224
23337
|
const project = loadProject();
|
|
22225
|
-
const skillsDir =
|
|
22226
|
-
|
|
22227
|
-
const skillDir =
|
|
22228
|
-
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)) {
|
|
22229
23342
|
console.log(chalk10.yellow(`Skill directory already exists: ${skillDir}`));
|
|
22230
23343
|
return;
|
|
22231
23344
|
}
|
|
22232
|
-
|
|
23345
|
+
fs15.mkdirSync(skillDir, { recursive: true });
|
|
22233
23346
|
const displayName = name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
22234
23347
|
const frontmatter = {
|
|
22235
23348
|
name,
|
|
@@ -22243,7 +23356,7 @@ async function skillsCreateCommand(name) {
|
|
|
22243
23356
|
You have the **${displayName}** skill.
|
|
22244
23357
|
`;
|
|
22245
23358
|
const skillMd = matter3.stringify(body, frontmatter);
|
|
22246
|
-
|
|
23359
|
+
fs15.writeFileSync(path16.join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
22247
23360
|
const actions = [
|
|
22248
23361
|
{
|
|
22249
23362
|
id: "run",
|
|
@@ -22253,7 +23366,7 @@ You have the **${displayName}** skill.
|
|
|
22253
23366
|
maxTurns: 5
|
|
22254
23367
|
}
|
|
22255
23368
|
];
|
|
22256
|
-
|
|
23369
|
+
fs15.writeFileSync(path16.join(skillDir, "actions.yaml"), YAML7.stringify(actions), "utf-8");
|
|
22257
23370
|
console.log(chalk10.green(`Created skill: ${skillDir}/`));
|
|
22258
23371
|
console.log(chalk10.dim(" SKILL.md \u2014 skill definition and prompt"));
|
|
22259
23372
|
console.log(chalk10.dim(" actions.yaml \u2014 action definitions"));
|
|
@@ -22261,14 +23374,14 @@ You have the **${displayName}** skill.
|
|
|
22261
23374
|
}
|
|
22262
23375
|
async function skillsMigrateCommand() {
|
|
22263
23376
|
const project = loadProject();
|
|
22264
|
-
const skillsDir =
|
|
22265
|
-
if (!
|
|
23377
|
+
const skillsDir = path16.join(project.marvinDir, "skills");
|
|
23378
|
+
if (!fs15.existsSync(skillsDir)) {
|
|
22266
23379
|
console.log(chalk10.dim("No skills directory found."));
|
|
22267
23380
|
return;
|
|
22268
23381
|
}
|
|
22269
23382
|
let entries;
|
|
22270
23383
|
try {
|
|
22271
|
-
entries =
|
|
23384
|
+
entries = fs15.readdirSync(skillsDir);
|
|
22272
23385
|
} catch {
|
|
22273
23386
|
console.log(chalk10.red("Could not read skills directory."));
|
|
22274
23387
|
return;
|
|
@@ -22280,16 +23393,16 @@ async function skillsMigrateCommand() {
|
|
|
22280
23393
|
}
|
|
22281
23394
|
let migrated = 0;
|
|
22282
23395
|
for (const file2 of yamlFiles) {
|
|
22283
|
-
const yamlPath =
|
|
23396
|
+
const yamlPath = path16.join(skillsDir, file2);
|
|
22284
23397
|
const baseName = file2.replace(/\.(yaml|yml)$/, "");
|
|
22285
|
-
const outputDir =
|
|
22286
|
-
if (
|
|
23398
|
+
const outputDir = path16.join(skillsDir, baseName);
|
|
23399
|
+
if (fs15.existsSync(outputDir)) {
|
|
22287
23400
|
console.log(chalk10.yellow(`Skipping "${file2}" \u2014 directory "${baseName}/" already exists.`));
|
|
22288
23401
|
continue;
|
|
22289
23402
|
}
|
|
22290
23403
|
try {
|
|
22291
23404
|
migrateYamlToSkillMd(yamlPath, outputDir);
|
|
22292
|
-
|
|
23405
|
+
fs15.renameSync(yamlPath, `${yamlPath}.bak`);
|
|
22293
23406
|
console.log(chalk10.green(`Migrated "${file2}" \u2192 "${baseName}/"`));
|
|
22294
23407
|
migrated++;
|
|
22295
23408
|
} catch (err) {
|
|
@@ -22303,35 +23416,35 @@ ${migrated} skill(s) migrated. Original files renamed to *.bak`));
|
|
|
22303
23416
|
}
|
|
22304
23417
|
|
|
22305
23418
|
// src/cli/commands/import.ts
|
|
22306
|
-
import * as
|
|
22307
|
-
import * as
|
|
23419
|
+
import * as fs18 from "fs";
|
|
23420
|
+
import * as path19 from "path";
|
|
22308
23421
|
import chalk11 from "chalk";
|
|
22309
23422
|
|
|
22310
23423
|
// src/import/engine.ts
|
|
22311
|
-
import * as
|
|
22312
|
-
import * as
|
|
23424
|
+
import * as fs17 from "fs";
|
|
23425
|
+
import * as path18 from "path";
|
|
22313
23426
|
import matter5 from "gray-matter";
|
|
22314
23427
|
|
|
22315
23428
|
// src/import/classifier.ts
|
|
22316
|
-
import * as
|
|
22317
|
-
import * as
|
|
23429
|
+
import * as fs16 from "fs";
|
|
23430
|
+
import * as path17 from "path";
|
|
22318
23431
|
import matter4 from "gray-matter";
|
|
22319
23432
|
var RAW_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".pdf", ".txt"]);
|
|
22320
23433
|
var CORE_DIR_NAMES = /* @__PURE__ */ new Set(["decisions", "actions", "questions"]);
|
|
22321
23434
|
var ID_PATTERN = /^[A-Z]+-\d{3,}$/;
|
|
22322
23435
|
function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
22323
|
-
const resolved =
|
|
22324
|
-
const stat =
|
|
23436
|
+
const resolved = path17.resolve(inputPath);
|
|
23437
|
+
const stat = fs16.statSync(resolved);
|
|
22325
23438
|
if (!stat.isDirectory()) {
|
|
22326
23439
|
return classifyFile(resolved, knownTypes);
|
|
22327
23440
|
}
|
|
22328
|
-
if (
|
|
23441
|
+
if (path17.basename(resolved) === ".marvin" || fs16.existsSync(path17.join(resolved, "config.yaml"))) {
|
|
22329
23442
|
return { type: "marvin-project", inputPath: resolved };
|
|
22330
23443
|
}
|
|
22331
23444
|
const allDirNames = /* @__PURE__ */ new Set([...CORE_DIR_NAMES, ...knownDirNames]);
|
|
22332
|
-
const entries =
|
|
23445
|
+
const entries = fs16.readdirSync(resolved);
|
|
22333
23446
|
const hasDocSubdirs = entries.some(
|
|
22334
|
-
(e) => allDirNames.has(e) &&
|
|
23447
|
+
(e) => allDirNames.has(e) && fs16.statSync(path17.join(resolved, e)).isDirectory()
|
|
22335
23448
|
);
|
|
22336
23449
|
if (hasDocSubdirs) {
|
|
22337
23450
|
return { type: "docs-directory", inputPath: resolved };
|
|
@@ -22340,7 +23453,7 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22340
23453
|
if (mdFiles.length > 0) {
|
|
22341
23454
|
const hasMarvinDocs = mdFiles.some((f) => {
|
|
22342
23455
|
try {
|
|
22343
|
-
const raw =
|
|
23456
|
+
const raw = fs16.readFileSync(path17.join(resolved, f), "utf-8");
|
|
22344
23457
|
const { data } = matter4(raw);
|
|
22345
23458
|
return isValidMarvinDocument(data, knownTypes);
|
|
22346
23459
|
} catch {
|
|
@@ -22354,14 +23467,14 @@ function classifyPath(inputPath, knownTypes, knownDirNames) {
|
|
|
22354
23467
|
return { type: "raw-source-dir", inputPath: resolved };
|
|
22355
23468
|
}
|
|
22356
23469
|
function classifyFile(filePath, knownTypes) {
|
|
22357
|
-
const resolved =
|
|
22358
|
-
const ext =
|
|
23470
|
+
const resolved = path17.resolve(filePath);
|
|
23471
|
+
const ext = path17.extname(resolved).toLowerCase();
|
|
22359
23472
|
if (RAW_SOURCE_EXTENSIONS.has(ext)) {
|
|
22360
23473
|
return { type: "raw-source-file", inputPath: resolved };
|
|
22361
23474
|
}
|
|
22362
23475
|
if (ext === ".md") {
|
|
22363
23476
|
try {
|
|
22364
|
-
const raw =
|
|
23477
|
+
const raw = fs16.readFileSync(resolved, "utf-8");
|
|
22365
23478
|
const { data } = matter4(raw);
|
|
22366
23479
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22367
23480
|
return { type: "marvin-document", inputPath: resolved };
|
|
@@ -22486,9 +23599,9 @@ function executeImportPlan(plan, store, marvinDir, options) {
|
|
|
22486
23599
|
continue;
|
|
22487
23600
|
}
|
|
22488
23601
|
if (item.action === "copy") {
|
|
22489
|
-
const targetDir =
|
|
22490
|
-
|
|
22491
|
-
|
|
23602
|
+
const targetDir = path18.dirname(item.targetPath);
|
|
23603
|
+
fs17.mkdirSync(targetDir, { recursive: true });
|
|
23604
|
+
fs17.copyFileSync(item.sourcePath, item.targetPath);
|
|
22492
23605
|
copied++;
|
|
22493
23606
|
continue;
|
|
22494
23607
|
}
|
|
@@ -22524,19 +23637,19 @@ function formatPlanSummary(plan) {
|
|
|
22524
23637
|
lines.push(`Documents to import: ${imports.length}`);
|
|
22525
23638
|
for (const item of imports) {
|
|
22526
23639
|
const idInfo = item.originalId !== item.newId ? `${item.originalId} \u2192 ${item.newId}` : item.newId ?? item.originalId ?? "";
|
|
22527
|
-
lines.push(` ${idInfo} ${
|
|
23640
|
+
lines.push(` ${idInfo} ${path18.basename(item.sourcePath)}`);
|
|
22528
23641
|
}
|
|
22529
23642
|
}
|
|
22530
23643
|
if (copies.length > 0) {
|
|
22531
23644
|
lines.push(`Files to copy to sources/: ${copies.length}`);
|
|
22532
23645
|
for (const item of copies) {
|
|
22533
|
-
lines.push(` ${
|
|
23646
|
+
lines.push(` ${path18.basename(item.sourcePath)} \u2192 ${path18.basename(item.targetPath)}`);
|
|
22534
23647
|
}
|
|
22535
23648
|
}
|
|
22536
23649
|
if (skips.length > 0) {
|
|
22537
23650
|
lines.push(`Skipped (conflict): ${skips.length}`);
|
|
22538
23651
|
for (const item of skips) {
|
|
22539
|
-
lines.push(` ${item.originalId ??
|
|
23652
|
+
lines.push(` ${item.originalId ?? path18.basename(item.sourcePath)} ${item.reason ?? ""}`);
|
|
22540
23653
|
}
|
|
22541
23654
|
}
|
|
22542
23655
|
if (plan.items.length === 0) {
|
|
@@ -22569,11 +23682,11 @@ function getDirNameForType(store, type) {
|
|
|
22569
23682
|
}
|
|
22570
23683
|
function collectMarvinDocs(dir, knownTypes) {
|
|
22571
23684
|
const docs = [];
|
|
22572
|
-
const files =
|
|
23685
|
+
const files = fs17.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
22573
23686
|
for (const file2 of files) {
|
|
22574
|
-
const filePath =
|
|
23687
|
+
const filePath = path18.join(dir, file2);
|
|
22575
23688
|
try {
|
|
22576
|
-
const raw =
|
|
23689
|
+
const raw = fs17.readFileSync(filePath, "utf-8");
|
|
22577
23690
|
const { data, content } = matter5(raw);
|
|
22578
23691
|
if (isValidMarvinDocument(data, knownTypes)) {
|
|
22579
23692
|
docs.push({
|
|
@@ -22629,23 +23742,23 @@ function planDocImports(docs, store, options) {
|
|
|
22629
23742
|
}
|
|
22630
23743
|
function planFromMarvinProject(classification, store, _marvinDir, options) {
|
|
22631
23744
|
let projectDir = classification.inputPath;
|
|
22632
|
-
if (
|
|
22633
|
-
const inner =
|
|
22634
|
-
if (
|
|
23745
|
+
if (path18.basename(projectDir) !== ".marvin") {
|
|
23746
|
+
const inner = path18.join(projectDir, ".marvin");
|
|
23747
|
+
if (fs17.existsSync(inner)) {
|
|
22635
23748
|
projectDir = inner;
|
|
22636
23749
|
}
|
|
22637
23750
|
}
|
|
22638
|
-
const docsDir =
|
|
22639
|
-
if (!
|
|
23751
|
+
const docsDir = path18.join(projectDir, "docs");
|
|
23752
|
+
if (!fs17.existsSync(docsDir)) {
|
|
22640
23753
|
return [];
|
|
22641
23754
|
}
|
|
22642
23755
|
const knownTypes = store.registeredTypes;
|
|
22643
23756
|
const allDocs = [];
|
|
22644
|
-
const subdirs =
|
|
22645
|
-
(d) =>
|
|
23757
|
+
const subdirs = fs17.readdirSync(docsDir).filter(
|
|
23758
|
+
(d) => fs17.statSync(path18.join(docsDir, d)).isDirectory()
|
|
22646
23759
|
);
|
|
22647
23760
|
for (const subdir of subdirs) {
|
|
22648
|
-
const docs = collectMarvinDocs(
|
|
23761
|
+
const docs = collectMarvinDocs(path18.join(docsDir, subdir), knownTypes);
|
|
22649
23762
|
allDocs.push(...docs);
|
|
22650
23763
|
}
|
|
22651
23764
|
return planDocImports(allDocs, store, options);
|
|
@@ -22655,10 +23768,10 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22655
23768
|
const knownTypes = store.registeredTypes;
|
|
22656
23769
|
const allDocs = [];
|
|
22657
23770
|
allDocs.push(...collectMarvinDocs(dir, knownTypes));
|
|
22658
|
-
const entries =
|
|
23771
|
+
const entries = fs17.readdirSync(dir);
|
|
22659
23772
|
for (const entry of entries) {
|
|
22660
|
-
const entryPath =
|
|
22661
|
-
if (
|
|
23773
|
+
const entryPath = path18.join(dir, entry);
|
|
23774
|
+
if (fs17.statSync(entryPath).isDirectory()) {
|
|
22662
23775
|
allDocs.push(...collectMarvinDocs(entryPath, knownTypes));
|
|
22663
23776
|
}
|
|
22664
23777
|
}
|
|
@@ -22667,7 +23780,7 @@ function planFromDocsDirectory(classification, store, _marvinDir, options) {
|
|
|
22667
23780
|
function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
22668
23781
|
const filePath = classification.inputPath;
|
|
22669
23782
|
const knownTypes = store.registeredTypes;
|
|
22670
|
-
const raw =
|
|
23783
|
+
const raw = fs17.readFileSync(filePath, "utf-8");
|
|
22671
23784
|
const { data, content } = matter5(raw);
|
|
22672
23785
|
if (!isValidMarvinDocument(data, knownTypes)) {
|
|
22673
23786
|
return [];
|
|
@@ -22683,14 +23796,14 @@ function planFromSingleDocument(classification, store, _marvinDir, options) {
|
|
|
22683
23796
|
}
|
|
22684
23797
|
function planFromRawSourceDir(classification, marvinDir) {
|
|
22685
23798
|
const dir = classification.inputPath;
|
|
22686
|
-
const sourcesDir =
|
|
23799
|
+
const sourcesDir = path18.join(marvinDir, "sources");
|
|
22687
23800
|
const items = [];
|
|
22688
|
-
const files =
|
|
22689
|
-
const stat =
|
|
23801
|
+
const files = fs17.readdirSync(dir).filter((f) => {
|
|
23802
|
+
const stat = fs17.statSync(path18.join(dir, f));
|
|
22690
23803
|
return stat.isFile();
|
|
22691
23804
|
});
|
|
22692
23805
|
for (const file2 of files) {
|
|
22693
|
-
const sourcePath =
|
|
23806
|
+
const sourcePath = path18.join(dir, file2);
|
|
22694
23807
|
const targetPath = resolveSourceFileName(sourcesDir, file2);
|
|
22695
23808
|
items.push({
|
|
22696
23809
|
action: "copy",
|
|
@@ -22701,8 +23814,8 @@ function planFromRawSourceDir(classification, marvinDir) {
|
|
|
22701
23814
|
return items;
|
|
22702
23815
|
}
|
|
22703
23816
|
function planFromRawSourceFile(classification, marvinDir) {
|
|
22704
|
-
const sourcesDir =
|
|
22705
|
-
const fileName =
|
|
23817
|
+
const sourcesDir = path18.join(marvinDir, "sources");
|
|
23818
|
+
const fileName = path18.basename(classification.inputPath);
|
|
22706
23819
|
const targetPath = resolveSourceFileName(sourcesDir, fileName);
|
|
22707
23820
|
return [
|
|
22708
23821
|
{
|
|
@@ -22713,25 +23826,25 @@ function planFromRawSourceFile(classification, marvinDir) {
|
|
|
22713
23826
|
];
|
|
22714
23827
|
}
|
|
22715
23828
|
function resolveSourceFileName(sourcesDir, fileName) {
|
|
22716
|
-
const targetPath =
|
|
22717
|
-
if (!
|
|
23829
|
+
const targetPath = path18.join(sourcesDir, fileName);
|
|
23830
|
+
if (!fs17.existsSync(targetPath)) {
|
|
22718
23831
|
return targetPath;
|
|
22719
23832
|
}
|
|
22720
|
-
const ext =
|
|
22721
|
-
const base =
|
|
23833
|
+
const ext = path18.extname(fileName);
|
|
23834
|
+
const base = path18.basename(fileName, ext);
|
|
22722
23835
|
let counter = 1;
|
|
22723
23836
|
let candidate;
|
|
22724
23837
|
do {
|
|
22725
|
-
candidate =
|
|
23838
|
+
candidate = path18.join(sourcesDir, `${base}-${counter}${ext}`);
|
|
22726
23839
|
counter++;
|
|
22727
|
-
} while (
|
|
23840
|
+
} while (fs17.existsSync(candidate));
|
|
22728
23841
|
return candidate;
|
|
22729
23842
|
}
|
|
22730
23843
|
|
|
22731
23844
|
// src/cli/commands/import.ts
|
|
22732
23845
|
async function importCommand(inputPath, options) {
|
|
22733
|
-
const resolved =
|
|
22734
|
-
if (!
|
|
23846
|
+
const resolved = path19.resolve(inputPath);
|
|
23847
|
+
if (!fs18.existsSync(resolved)) {
|
|
22735
23848
|
throw new ImportError(`Path not found: ${resolved}`);
|
|
22736
23849
|
}
|
|
22737
23850
|
const project = loadProject();
|
|
@@ -22783,7 +23896,7 @@ async function importCommand(inputPath, options) {
|
|
|
22783
23896
|
console.log(chalk11.bold("\nStarting ingest of copied sources...\n"));
|
|
22784
23897
|
const manifest = new SourceManifestManager(marvinDir);
|
|
22785
23898
|
manifest.scan();
|
|
22786
|
-
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));
|
|
22787
23900
|
for (const fileName of copiedFileNames) {
|
|
22788
23901
|
try {
|
|
22789
23902
|
await ingestFile({
|
|
@@ -23672,14 +24785,14 @@ async function webCommand(options) {
|
|
|
23672
24785
|
}
|
|
23673
24786
|
|
|
23674
24787
|
// src/cli/commands/generate.ts
|
|
23675
|
-
import * as
|
|
23676
|
-
import * as
|
|
24788
|
+
import * as fs19 from "fs";
|
|
24789
|
+
import * as path20 from "path";
|
|
23677
24790
|
import chalk18 from "chalk";
|
|
23678
24791
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
23679
24792
|
async function generateClaudeMdCommand(options) {
|
|
23680
24793
|
const project = loadProject();
|
|
23681
|
-
const filePath =
|
|
23682
|
-
if (
|
|
24794
|
+
const filePath = path20.join(project.marvinDir, "CLAUDE.md");
|
|
24795
|
+
if (fs19.existsSync(filePath) && !options.force) {
|
|
23683
24796
|
const overwrite = await confirm2({
|
|
23684
24797
|
message: ".marvin/CLAUDE.md already exists. Overwrite?",
|
|
23685
24798
|
default: false
|
|
@@ -23689,7 +24802,7 @@ async function generateClaudeMdCommand(options) {
|
|
|
23689
24802
|
return;
|
|
23690
24803
|
}
|
|
23691
24804
|
}
|
|
23692
|
-
|
|
24805
|
+
fs19.writeFileSync(
|
|
23693
24806
|
filePath,
|
|
23694
24807
|
getDefaultClaudeMdContent(project.config.name),
|
|
23695
24808
|
"utf-8"
|
|
@@ -23702,7 +24815,7 @@ function createProgram() {
|
|
|
23702
24815
|
const program2 = new Command();
|
|
23703
24816
|
program2.name("marvin").description(
|
|
23704
24817
|
"AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
|
|
23705
|
-
).version("0.4.
|
|
24818
|
+
).version("0.4.4");
|
|
23706
24819
|
program2.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
|
|
23707
24820
|
await initCommand();
|
|
23708
24821
|
});
|