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-serve.js
CHANGED
|
@@ -6,7 +6,7 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// bin/marvin-serve.ts
|
|
9
|
-
import * as
|
|
9
|
+
import * as path10 from "path";
|
|
10
10
|
|
|
11
11
|
// src/core/project.ts
|
|
12
12
|
import * as fs2 from "fs";
|
|
@@ -98,8 +98,8 @@ function findProjectRoot(from = process.cwd()) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// src/mcp/stdio-server.ts
|
|
101
|
-
import * as
|
|
102
|
-
import * as
|
|
101
|
+
import * as fs9 from "fs";
|
|
102
|
+
import * as path9 from "path";
|
|
103
103
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
104
104
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
105
105
|
|
|
@@ -1206,10 +1206,10 @@ function mergeDefs(...defs) {
|
|
|
1206
1206
|
function cloneDef(schema) {
|
|
1207
1207
|
return mergeDefs(schema._zod.def);
|
|
1208
1208
|
}
|
|
1209
|
-
function getElementAtPath(obj,
|
|
1210
|
-
if (!
|
|
1209
|
+
function getElementAtPath(obj, path11) {
|
|
1210
|
+
if (!path11)
|
|
1211
1211
|
return obj;
|
|
1212
|
-
return
|
|
1212
|
+
return path11.reduce((acc, key) => acc?.[key], obj);
|
|
1213
1213
|
}
|
|
1214
1214
|
function promiseAllObject(promisesObj) {
|
|
1215
1215
|
const keys = Object.keys(promisesObj);
|
|
@@ -1592,11 +1592,11 @@ function aborted(x, startIndex = 0) {
|
|
|
1592
1592
|
}
|
|
1593
1593
|
return false;
|
|
1594
1594
|
}
|
|
1595
|
-
function prefixIssues(
|
|
1595
|
+
function prefixIssues(path11, issues) {
|
|
1596
1596
|
return issues.map((iss) => {
|
|
1597
1597
|
var _a2;
|
|
1598
1598
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
1599
|
-
iss.path.unshift(
|
|
1599
|
+
iss.path.unshift(path11);
|
|
1600
1600
|
return iss;
|
|
1601
1601
|
});
|
|
1602
1602
|
}
|
|
@@ -1779,7 +1779,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1779
1779
|
}
|
|
1780
1780
|
function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
1781
1781
|
const result = { errors: [] };
|
|
1782
|
-
const processError = (error49,
|
|
1782
|
+
const processError = (error49, path11 = []) => {
|
|
1783
1783
|
var _a2, _b;
|
|
1784
1784
|
for (const issue2 of error49.issues) {
|
|
1785
1785
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -1789,7 +1789,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1789
1789
|
} else if (issue2.code === "invalid_element") {
|
|
1790
1790
|
processError({ issues: issue2.issues }, issue2.path);
|
|
1791
1791
|
} else {
|
|
1792
|
-
const fullpath = [...
|
|
1792
|
+
const fullpath = [...path11, ...issue2.path];
|
|
1793
1793
|
if (fullpath.length === 0) {
|
|
1794
1794
|
result.errors.push(mapper(issue2));
|
|
1795
1795
|
continue;
|
|
@@ -1821,8 +1821,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1821
1821
|
}
|
|
1822
1822
|
function toDotPath(_path) {
|
|
1823
1823
|
const segs = [];
|
|
1824
|
-
const
|
|
1825
|
-
for (const seg of
|
|
1824
|
+
const path11 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1825
|
+
for (const seg of path11) {
|
|
1826
1826
|
if (typeof seg === "number")
|
|
1827
1827
|
segs.push(`[${seg}]`);
|
|
1828
1828
|
else if (typeof seg === "symbol")
|
|
@@ -13799,13 +13799,13 @@ function resolveRef(ref, ctx) {
|
|
|
13799
13799
|
if (!ref.startsWith("#")) {
|
|
13800
13800
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
13801
13801
|
}
|
|
13802
|
-
const
|
|
13803
|
-
if (
|
|
13802
|
+
const path11 = ref.slice(1).split("/").filter(Boolean);
|
|
13803
|
+
if (path11.length === 0) {
|
|
13804
13804
|
return ctx.rootSchema;
|
|
13805
13805
|
}
|
|
13806
13806
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
13807
|
-
if (
|
|
13808
|
-
const key =
|
|
13807
|
+
if (path11[0] === defsKey) {
|
|
13808
|
+
const key = path11[1];
|
|
13809
13809
|
if (!key || !ctx.defs[key]) {
|
|
13810
13810
|
throw new Error(`Reference not found: ${ref}`);
|
|
13811
13811
|
}
|
|
@@ -15092,7 +15092,14 @@ import { tool as tool8 } from "@anthropic-ai/claude-agent-sdk";
|
|
|
15092
15092
|
// src/plugins/builtin/tools/epic-utils.ts
|
|
15093
15093
|
function normalizeLinkedFeatures(value) {
|
|
15094
15094
|
if (value === void 0 || value === null) return [];
|
|
15095
|
-
if (typeof value === "string")
|
|
15095
|
+
if (typeof value === "string") {
|
|
15096
|
+
try {
|
|
15097
|
+
const parsed = JSON.parse(value);
|
|
15098
|
+
if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
|
|
15099
|
+
} catch {
|
|
15100
|
+
}
|
|
15101
|
+
return [value];
|
|
15102
|
+
}
|
|
15096
15103
|
if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
|
|
15097
15104
|
return [];
|
|
15098
15105
|
}
|
|
@@ -15915,6 +15922,20 @@ function createFeatureTools(store) {
|
|
|
15915
15922
|
|
|
15916
15923
|
// src/plugins/builtin/tools/epics.ts
|
|
15917
15924
|
import { tool as tool10 } from "@anthropic-ai/claude-agent-sdk";
|
|
15925
|
+
var linkedFeatureArray = external_exports.preprocess(
|
|
15926
|
+
(val) => {
|
|
15927
|
+
if (typeof val === "string") {
|
|
15928
|
+
try {
|
|
15929
|
+
const parsed = JSON.parse(val);
|
|
15930
|
+
if (Array.isArray(parsed)) return parsed;
|
|
15931
|
+
} catch {
|
|
15932
|
+
}
|
|
15933
|
+
return [val];
|
|
15934
|
+
}
|
|
15935
|
+
return val;
|
|
15936
|
+
},
|
|
15937
|
+
external_exports.array(external_exports.string())
|
|
15938
|
+
);
|
|
15918
15939
|
function createEpicTools(store) {
|
|
15919
15940
|
return [
|
|
15920
15941
|
tool10(
|
|
@@ -15980,7 +16001,7 @@ function createEpicTools(store) {
|
|
|
15980
16001
|
{
|
|
15981
16002
|
title: external_exports.string().describe("Epic title"),
|
|
15982
16003
|
content: external_exports.string().describe("Epic description and scope"),
|
|
15983
|
-
linkedFeature:
|
|
16004
|
+
linkedFeature: linkedFeatureArray.describe("Feature ID(s) to link this epic to (e.g. ['F-001'] or ['F-001', 'F-002'])"),
|
|
15984
16005
|
status: external_exports.enum(["planned", "in-progress", "done"]).optional().describe("Epic status (default: 'planned')"),
|
|
15985
16006
|
owner: external_exports.string().optional().describe("Epic owner"),
|
|
15986
16007
|
targetDate: external_exports.string().optional().describe("Target completion date (ISO format)"),
|
|
@@ -16056,7 +16077,7 @@ function createEpicTools(store) {
|
|
|
16056
16077
|
owner: external_exports.string().optional().describe("New owner"),
|
|
16057
16078
|
targetDate: external_exports.string().optional().describe("New target date"),
|
|
16058
16079
|
estimatedEffort: external_exports.string().optional().describe("New estimated effort"),
|
|
16059
|
-
linkedFeature:
|
|
16080
|
+
linkedFeature: linkedFeatureArray.optional().describe("New linked feature ID(s)"),
|
|
16060
16081
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove 'risk', add 'risk-mitigated')")
|
|
16061
16082
|
},
|
|
16062
16083
|
async (args) => {
|
|
@@ -16593,6 +16614,205 @@ function createSprintPlanningTools(store) {
|
|
|
16593
16614
|
];
|
|
16594
16615
|
}
|
|
16595
16616
|
|
|
16617
|
+
// src/plugins/builtin/tools/tasks.ts
|
|
16618
|
+
import { tool as tool14 } from "@anthropic-ai/claude-agent-sdk";
|
|
16619
|
+
|
|
16620
|
+
// src/plugins/builtin/tools/task-utils.ts
|
|
16621
|
+
function normalizeLinkedEpics(value) {
|
|
16622
|
+
if (value === void 0 || value === null) return [];
|
|
16623
|
+
if (typeof value === "string") {
|
|
16624
|
+
try {
|
|
16625
|
+
const parsed = JSON.parse(value);
|
|
16626
|
+
if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
|
|
16627
|
+
} catch {
|
|
16628
|
+
}
|
|
16629
|
+
return [value];
|
|
16630
|
+
}
|
|
16631
|
+
if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
|
|
16632
|
+
return [];
|
|
16633
|
+
}
|
|
16634
|
+
function generateEpicTags(epics) {
|
|
16635
|
+
return epics.map((id) => `epic:${id}`);
|
|
16636
|
+
}
|
|
16637
|
+
|
|
16638
|
+
// src/plugins/builtin/tools/tasks.ts
|
|
16639
|
+
var linkedEpicArray = external_exports.preprocess(
|
|
16640
|
+
(val) => {
|
|
16641
|
+
if (typeof val === "string") {
|
|
16642
|
+
try {
|
|
16643
|
+
const parsed = JSON.parse(val);
|
|
16644
|
+
if (Array.isArray(parsed)) return parsed;
|
|
16645
|
+
} catch {
|
|
16646
|
+
}
|
|
16647
|
+
return [val];
|
|
16648
|
+
}
|
|
16649
|
+
return val;
|
|
16650
|
+
},
|
|
16651
|
+
external_exports.array(external_exports.string())
|
|
16652
|
+
);
|
|
16653
|
+
function createTaskTools(store) {
|
|
16654
|
+
return [
|
|
16655
|
+
tool14(
|
|
16656
|
+
"list_tasks",
|
|
16657
|
+
"List all tasks in the project, optionally filtered by status, linked epic, or priority",
|
|
16658
|
+
{
|
|
16659
|
+
status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("Filter by task status"),
|
|
16660
|
+
linkedEpic: external_exports.string().optional().describe("Filter by linked epic ID (e.g. 'E-001')"),
|
|
16661
|
+
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Filter by priority")
|
|
16662
|
+
},
|
|
16663
|
+
async (args) => {
|
|
16664
|
+
let docs = store.list({ type: "task", status: args.status });
|
|
16665
|
+
if (args.linkedEpic) {
|
|
16666
|
+
docs = docs.filter(
|
|
16667
|
+
(d) => normalizeLinkedEpics(d.frontmatter.linkedEpic).includes(args.linkedEpic)
|
|
16668
|
+
);
|
|
16669
|
+
}
|
|
16670
|
+
if (args.priority) {
|
|
16671
|
+
docs = docs.filter((d) => d.frontmatter.priority === args.priority);
|
|
16672
|
+
}
|
|
16673
|
+
const summary = docs.map((d) => ({
|
|
16674
|
+
id: d.frontmatter.id,
|
|
16675
|
+
title: d.frontmatter.title,
|
|
16676
|
+
status: d.frontmatter.status,
|
|
16677
|
+
linkedEpic: normalizeLinkedEpics(d.frontmatter.linkedEpic),
|
|
16678
|
+
priority: d.frontmatter.priority,
|
|
16679
|
+
complexity: d.frontmatter.complexity,
|
|
16680
|
+
estimatedPoints: d.frontmatter.estimatedPoints,
|
|
16681
|
+
tags: d.frontmatter.tags
|
|
16682
|
+
}));
|
|
16683
|
+
return {
|
|
16684
|
+
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
16685
|
+
};
|
|
16686
|
+
},
|
|
16687
|
+
{ annotations: { readOnlyHint: true } }
|
|
16688
|
+
),
|
|
16689
|
+
tool14(
|
|
16690
|
+
"get_task",
|
|
16691
|
+
"Get the full content of a specific task by ID",
|
|
16692
|
+
{ id: external_exports.string().describe("Task ID (e.g. 'T-001')") },
|
|
16693
|
+
async (args) => {
|
|
16694
|
+
const doc = store.get(args.id);
|
|
16695
|
+
if (!doc) {
|
|
16696
|
+
return {
|
|
16697
|
+
content: [{ type: "text", text: `Task ${args.id} not found` }],
|
|
16698
|
+
isError: true
|
|
16699
|
+
};
|
|
16700
|
+
}
|
|
16701
|
+
return {
|
|
16702
|
+
content: [
|
|
16703
|
+
{
|
|
16704
|
+
type: "text",
|
|
16705
|
+
text: JSON.stringify(
|
|
16706
|
+
{ ...doc.frontmatter, content: doc.content },
|
|
16707
|
+
null,
|
|
16708
|
+
2
|
|
16709
|
+
)
|
|
16710
|
+
}
|
|
16711
|
+
]
|
|
16712
|
+
};
|
|
16713
|
+
},
|
|
16714
|
+
{ annotations: { readOnlyHint: true } }
|
|
16715
|
+
),
|
|
16716
|
+
tool14(
|
|
16717
|
+
"create_task",
|
|
16718
|
+
"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).",
|
|
16719
|
+
{
|
|
16720
|
+
title: external_exports.string().describe("Task title"),
|
|
16721
|
+
content: external_exports.string().describe("Task description and implementation details"),
|
|
16722
|
+
linkedEpic: linkedEpicArray.describe("Epic ID(s) to link this task to (e.g. ['E-001'] or ['E-001', 'E-002'])"),
|
|
16723
|
+
status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("Task status (default: 'backlog')"),
|
|
16724
|
+
acceptanceCriteria: external_exports.string().optional().describe("Acceptance criteria for the task"),
|
|
16725
|
+
technicalNotes: external_exports.string().optional().describe("Technical implementation notes"),
|
|
16726
|
+
estimatedPoints: external_exports.number().optional().describe("Story point estimate"),
|
|
16727
|
+
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("Task complexity"),
|
|
16728
|
+
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("Task priority"),
|
|
16729
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Additional tags")
|
|
16730
|
+
},
|
|
16731
|
+
async (args) => {
|
|
16732
|
+
const linkedEpics = normalizeLinkedEpics(args.linkedEpic);
|
|
16733
|
+
const warnings = [];
|
|
16734
|
+
for (const epicId of linkedEpics) {
|
|
16735
|
+
const epic = store.get(epicId);
|
|
16736
|
+
if (!epic) {
|
|
16737
|
+
warnings.push(`Warning: Epic ${epicId} not found`);
|
|
16738
|
+
} else if (epic.frontmatter.type !== "epic") {
|
|
16739
|
+
warnings.push(`Warning: ${epicId} is a ${epic.frontmatter.type}, not an epic`);
|
|
16740
|
+
}
|
|
16741
|
+
}
|
|
16742
|
+
const frontmatter = {
|
|
16743
|
+
title: args.title,
|
|
16744
|
+
status: args.status ?? "backlog",
|
|
16745
|
+
linkedEpic: linkedEpics,
|
|
16746
|
+
tags: [...generateEpicTags(linkedEpics), ...args.tags ?? []]
|
|
16747
|
+
};
|
|
16748
|
+
if (args.acceptanceCriteria) frontmatter.acceptanceCriteria = args.acceptanceCriteria;
|
|
16749
|
+
if (args.technicalNotes) frontmatter.technicalNotes = args.technicalNotes;
|
|
16750
|
+
if (args.estimatedPoints !== void 0) frontmatter.estimatedPoints = args.estimatedPoints;
|
|
16751
|
+
if (args.complexity) frontmatter.complexity = args.complexity;
|
|
16752
|
+
if (args.priority) frontmatter.priority = args.priority;
|
|
16753
|
+
const doc = store.create("task", frontmatter, args.content);
|
|
16754
|
+
const parts = [
|
|
16755
|
+
`Created task ${doc.frontmatter.id}: ${doc.frontmatter.title} (linked to ${linkedEpics.join(", ")})`
|
|
16756
|
+
];
|
|
16757
|
+
if (warnings.length > 0) {
|
|
16758
|
+
parts.push(warnings.join("; "));
|
|
16759
|
+
}
|
|
16760
|
+
return {
|
|
16761
|
+
content: [{ type: "text", text: parts.join("\n") }]
|
|
16762
|
+
};
|
|
16763
|
+
}
|
|
16764
|
+
),
|
|
16765
|
+
tool14(
|
|
16766
|
+
"update_task",
|
|
16767
|
+
"Update an existing task, including its linked epics.",
|
|
16768
|
+
{
|
|
16769
|
+
id: external_exports.string().describe("Task ID to update"),
|
|
16770
|
+
title: external_exports.string().optional().describe("New title"),
|
|
16771
|
+
status: external_exports.enum(["backlog", "ready", "in-progress", "review", "done"]).optional().describe("New status"),
|
|
16772
|
+
content: external_exports.string().optional().describe("New content"),
|
|
16773
|
+
linkedEpic: linkedEpicArray.optional().describe("New linked epic ID(s)"),
|
|
16774
|
+
acceptanceCriteria: external_exports.string().optional().describe("New acceptance criteria"),
|
|
16775
|
+
technicalNotes: external_exports.string().optional().describe("New technical notes"),
|
|
16776
|
+
estimatedPoints: external_exports.number().optional().describe("New story point estimate"),
|
|
16777
|
+
complexity: external_exports.enum(["trivial", "simple", "moderate", "complex", "very-complex"]).optional().describe("New complexity"),
|
|
16778
|
+
priority: external_exports.enum(["critical", "high", "medium", "low"]).optional().describe("New priority"),
|
|
16779
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Replace tags (e.g. remove old tags, add new ones)")
|
|
16780
|
+
},
|
|
16781
|
+
async (args) => {
|
|
16782
|
+
const { id, content, linkedEpic: rawLinkedEpic, tags: userTags, ...updates } = args;
|
|
16783
|
+
const warnings = [];
|
|
16784
|
+
if (rawLinkedEpic !== void 0) {
|
|
16785
|
+
const linkedEpics = normalizeLinkedEpics(rawLinkedEpic);
|
|
16786
|
+
for (const epicId of linkedEpics) {
|
|
16787
|
+
const epic = store.get(epicId);
|
|
16788
|
+
if (!epic) {
|
|
16789
|
+
warnings.push(`Warning: Epic ${epicId} not found`);
|
|
16790
|
+
} else if (epic.frontmatter.type !== "epic") {
|
|
16791
|
+
warnings.push(`Warning: ${epicId} is a ${epic.frontmatter.type}, not an epic`);
|
|
16792
|
+
}
|
|
16793
|
+
}
|
|
16794
|
+
updates.linkedEpic = linkedEpics;
|
|
16795
|
+
const existingDoc = store.get(id);
|
|
16796
|
+
const existingTags = existingDoc?.frontmatter.tags ?? [];
|
|
16797
|
+
const nonEpicTags = existingTags.filter((t) => !t.startsWith("epic:"));
|
|
16798
|
+
const baseTags = userTags ?? nonEpicTags;
|
|
16799
|
+
updates.tags = [...generateEpicTags(linkedEpics), ...baseTags];
|
|
16800
|
+
} else if (userTags !== void 0) {
|
|
16801
|
+
updates.tags = userTags;
|
|
16802
|
+
}
|
|
16803
|
+
const doc = store.update(id, updates, content);
|
|
16804
|
+
const parts = [`Updated task ${doc.frontmatter.id}: ${doc.frontmatter.title}`];
|
|
16805
|
+
if (warnings.length > 0) {
|
|
16806
|
+
parts.push(warnings.join("; "));
|
|
16807
|
+
}
|
|
16808
|
+
return {
|
|
16809
|
+
content: [{ type: "text", text: parts.join("\n") }]
|
|
16810
|
+
};
|
|
16811
|
+
}
|
|
16812
|
+
)
|
|
16813
|
+
];
|
|
16814
|
+
}
|
|
16815
|
+
|
|
16596
16816
|
// src/plugins/common.ts
|
|
16597
16817
|
var COMMON_REGISTRATIONS = [
|
|
16598
16818
|
{ type: "meeting", dirName: "meetings", idPrefix: "M" },
|
|
@@ -16600,7 +16820,8 @@ var COMMON_REGISTRATIONS = [
|
|
|
16600
16820
|
{ type: "feature", dirName: "features", idPrefix: "F" },
|
|
16601
16821
|
{ type: "epic", dirName: "epics", idPrefix: "E" },
|
|
16602
16822
|
{ type: "contribution", dirName: "contributions", idPrefix: "C" },
|
|
16603
|
-
{ type: "sprint", dirName: "sprints", idPrefix: "SP" }
|
|
16823
|
+
{ type: "sprint", dirName: "sprints", idPrefix: "SP" },
|
|
16824
|
+
{ type: "task", dirName: "tasks", idPrefix: "T" }
|
|
16604
16825
|
];
|
|
16605
16826
|
function createCommonTools(store) {
|
|
16606
16827
|
return [
|
|
@@ -16610,7 +16831,8 @@ function createCommonTools(store) {
|
|
|
16610
16831
|
...createEpicTools(store),
|
|
16611
16832
|
...createContributionTools(store),
|
|
16612
16833
|
...createSprintTools(store),
|
|
16613
|
-
...createSprintPlanningTools(store)
|
|
16834
|
+
...createSprintPlanningTools(store),
|
|
16835
|
+
...createTaskTools(store)
|
|
16614
16836
|
];
|
|
16615
16837
|
}
|
|
16616
16838
|
|
|
@@ -16620,7 +16842,7 @@ var genericAgilePlugin = {
|
|
|
16620
16842
|
name: "Generic Agile",
|
|
16621
16843
|
description: "Default methodology plugin providing standard agile governance patterns for decisions, actions, and questions.",
|
|
16622
16844
|
version: "0.1.0",
|
|
16623
|
-
documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic", "contribution", "sprint"],
|
|
16845
|
+
documentTypes: ["decision", "action", "question", "meeting", "report", "feature", "epic", "contribution", "sprint", "task"],
|
|
16624
16846
|
documentTypeRegistrations: [...COMMON_REGISTRATIONS],
|
|
16625
16847
|
tools: (store) => [...createCommonTools(store)],
|
|
16626
16848
|
promptFragments: {
|
|
@@ -16659,6 +16881,11 @@ var genericAgilePlugin = {
|
|
|
16659
16881
|
- **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.
|
|
16660
16882
|
- **update_epic**: Update epic status (planned \u2192 in-progress \u2192 done), owner, and other fields.
|
|
16661
16883
|
|
|
16884
|
+
**Task Tools:**
|
|
16885
|
+
- **list_tasks** / **get_task**: Browse and read implementation tasks.
|
|
16886
|
+
- **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".
|
|
16887
|
+
- **update_task**: Update task status (backlog \u2192 ready \u2192 in-progress \u2192 review \u2192 done), acceptance criteria, technical notes, complexity, priority, and estimated points.
|
|
16888
|
+
|
|
16662
16889
|
**Feature Tools (read-only for awareness):**
|
|
16663
16890
|
- **list_features** / **get_feature**: View features to understand what needs to be broken into epics.
|
|
16664
16891
|
|
|
@@ -16670,6 +16897,7 @@ var genericAgilePlugin = {
|
|
|
16670
16897
|
|
|
16671
16898
|
**Key Workflow Rules:**
|
|
16672
16899
|
- Only create epics against approved features \u2014 create_epic enforces this.
|
|
16900
|
+
- Break epics into tasks (T-xxx) with clear acceptance criteria and complexity estimates.
|
|
16673
16901
|
- Tag work items (actions, decisions, questions) with \`epic:E-xxx\` to group them under an epic.
|
|
16674
16902
|
- Collaborate with the Delivery Manager on target dates and effort estimates.
|
|
16675
16903
|
- Each epic should have a clear scope and definition of done.
|
|
@@ -16705,6 +16933,9 @@ var genericAgilePlugin = {
|
|
|
16705
16933
|
- **list_epics** / **get_epic**: View epics and their current status.
|
|
16706
16934
|
- **update_epic**: Set targetDate and estimatedEffort on epics. Flag epics linked to deferred features.
|
|
16707
16935
|
|
|
16936
|
+
**Task Tools (read-only for tracking):**
|
|
16937
|
+
- **list_tasks** / **get_task**: View tasks and their statuses. Filter by linkedEpic to see implementation breakdown.
|
|
16938
|
+
|
|
16708
16939
|
**Feature Tools (tracking focus):**
|
|
16709
16940
|
- **list_features** / **get_feature**: View features and their priorities.
|
|
16710
16941
|
|
|
@@ -16750,14 +16981,15 @@ var genericAgilePlugin = {
|
|
|
16750
16981
|
- Reason through: priority (critical/high features first), capacity (compare backlog effort to velocity reference), dependencies and blockers, balance across features, and risk.
|
|
16751
16982
|
- Present a structured sprint proposal: title, goal, suggested dates, selected epics with rationale for each, excluded epics with reason, and identified risks.
|
|
16752
16983
|
- After user confirmation, use **create_sprint** with the agreed epics to persist the sprint.`,
|
|
16753
|
-
"*": `You have access to feature, epic, sprint, and meeting tools for project coordination:
|
|
16984
|
+
"*": `You have access to feature, epic, task, sprint, and meeting tools for project coordination:
|
|
16754
16985
|
|
|
16755
16986
|
**Features** (F-xxx): Product capabilities defined by the Product Owner. Features progress through draft \u2192 approved \u2192 done.
|
|
16756
16987
|
**Epics** (E-xxx): Implementation work packages created by the Tech Lead, linked to approved features. Epics progress through planned \u2192 in-progress \u2192 done.
|
|
16988
|
+
**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.
|
|
16757
16989
|
**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).
|
|
16758
16990
|
**Meetings**: Meeting records with attendees, agendas, and notes.
|
|
16759
16991
|
|
|
16760
|
-
**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.
|
|
16992
|
+
**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.
|
|
16761
16993
|
|
|
16762
16994
|
- **list_meetings** / **get_meeting**: Browse and read meeting records.
|
|
16763
16995
|
- **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.
|
|
@@ -16772,10 +17004,10 @@ var genericAgilePlugin = {
|
|
|
16772
17004
|
};
|
|
16773
17005
|
|
|
16774
17006
|
// src/plugins/builtin/tools/use-cases.ts
|
|
16775
|
-
import { tool as
|
|
17007
|
+
import { tool as tool15 } from "@anthropic-ai/claude-agent-sdk";
|
|
16776
17008
|
function createUseCaseTools(store) {
|
|
16777
17009
|
return [
|
|
16778
|
-
|
|
17010
|
+
tool15(
|
|
16779
17011
|
"list_use_cases",
|
|
16780
17012
|
"List all extension use cases, optionally filtered by status or extension type",
|
|
16781
17013
|
{
|
|
@@ -16805,7 +17037,7 @@ function createUseCaseTools(store) {
|
|
|
16805
17037
|
},
|
|
16806
17038
|
{ annotations: { readOnlyHint: true } }
|
|
16807
17039
|
),
|
|
16808
|
-
|
|
17040
|
+
tool15(
|
|
16809
17041
|
"get_use_case",
|
|
16810
17042
|
"Get the full content of a specific use case by ID",
|
|
16811
17043
|
{ id: external_exports.string().describe("Use case ID (e.g. 'UC-001')") },
|
|
@@ -16832,7 +17064,7 @@ function createUseCaseTools(store) {
|
|
|
16832
17064
|
},
|
|
16833
17065
|
{ annotations: { readOnlyHint: true } }
|
|
16834
17066
|
),
|
|
16835
|
-
|
|
17067
|
+
tool15(
|
|
16836
17068
|
"create_use_case",
|
|
16837
17069
|
"Create a new extension use case definition (Phase 1: Assess Extension Use Case)",
|
|
16838
17070
|
{
|
|
@@ -16866,7 +17098,7 @@ function createUseCaseTools(store) {
|
|
|
16866
17098
|
};
|
|
16867
17099
|
}
|
|
16868
17100
|
),
|
|
16869
|
-
|
|
17101
|
+
tool15(
|
|
16870
17102
|
"update_use_case",
|
|
16871
17103
|
"Update an existing extension use case",
|
|
16872
17104
|
{
|
|
@@ -16896,10 +17128,10 @@ function createUseCaseTools(store) {
|
|
|
16896
17128
|
}
|
|
16897
17129
|
|
|
16898
17130
|
// src/plugins/builtin/tools/tech-assessments.ts
|
|
16899
|
-
import { tool as
|
|
17131
|
+
import { tool as tool16 } from "@anthropic-ai/claude-agent-sdk";
|
|
16900
17132
|
function createTechAssessmentTools(store) {
|
|
16901
17133
|
return [
|
|
16902
|
-
|
|
17134
|
+
tool16(
|
|
16903
17135
|
"list_tech_assessments",
|
|
16904
17136
|
"List all technology assessments, optionally filtered by status",
|
|
16905
17137
|
{
|
|
@@ -16930,7 +17162,7 @@ function createTechAssessmentTools(store) {
|
|
|
16930
17162
|
},
|
|
16931
17163
|
{ annotations: { readOnlyHint: true } }
|
|
16932
17164
|
),
|
|
16933
|
-
|
|
17165
|
+
tool16(
|
|
16934
17166
|
"get_tech_assessment",
|
|
16935
17167
|
"Get the full content of a specific technology assessment by ID",
|
|
16936
17168
|
{ id: external_exports.string().describe("Tech assessment ID (e.g. 'TA-001')") },
|
|
@@ -16957,7 +17189,7 @@ function createTechAssessmentTools(store) {
|
|
|
16957
17189
|
},
|
|
16958
17190
|
{ annotations: { readOnlyHint: true } }
|
|
16959
17191
|
),
|
|
16960
|
-
|
|
17192
|
+
tool16(
|
|
16961
17193
|
"create_tech_assessment",
|
|
16962
17194
|
"Create a new technology assessment linked to an assessed/approved use case (Phase 2: Assess Extension Technology)",
|
|
16963
17195
|
{
|
|
@@ -17028,7 +17260,7 @@ function createTechAssessmentTools(store) {
|
|
|
17028
17260
|
};
|
|
17029
17261
|
}
|
|
17030
17262
|
),
|
|
17031
|
-
|
|
17263
|
+
tool16(
|
|
17032
17264
|
"update_tech_assessment",
|
|
17033
17265
|
"Update an existing technology assessment. The linked use case cannot be changed.",
|
|
17034
17266
|
{
|
|
@@ -17058,10 +17290,10 @@ function createTechAssessmentTools(store) {
|
|
|
17058
17290
|
}
|
|
17059
17291
|
|
|
17060
17292
|
// src/plugins/builtin/tools/extension-designs.ts
|
|
17061
|
-
import { tool as
|
|
17293
|
+
import { tool as tool17 } from "@anthropic-ai/claude-agent-sdk";
|
|
17062
17294
|
function createExtensionDesignTools(store) {
|
|
17063
17295
|
return [
|
|
17064
|
-
|
|
17296
|
+
tool17(
|
|
17065
17297
|
"list_extension_designs",
|
|
17066
17298
|
"List all extension designs, optionally filtered by status",
|
|
17067
17299
|
{
|
|
@@ -17091,7 +17323,7 @@ function createExtensionDesignTools(store) {
|
|
|
17091
17323
|
},
|
|
17092
17324
|
{ annotations: { readOnlyHint: true } }
|
|
17093
17325
|
),
|
|
17094
|
-
|
|
17326
|
+
tool17(
|
|
17095
17327
|
"get_extension_design",
|
|
17096
17328
|
"Get the full content of a specific extension design by ID",
|
|
17097
17329
|
{ id: external_exports.string().describe("Extension design ID (e.g. 'XD-001')") },
|
|
@@ -17118,7 +17350,7 @@ function createExtensionDesignTools(store) {
|
|
|
17118
17350
|
},
|
|
17119
17351
|
{ annotations: { readOnlyHint: true } }
|
|
17120
17352
|
),
|
|
17121
|
-
|
|
17353
|
+
tool17(
|
|
17122
17354
|
"create_extension_design",
|
|
17123
17355
|
"Create a new extension design linked to a recommended tech assessment (Phase 3: Define Extension Target Solution)",
|
|
17124
17356
|
{
|
|
@@ -17186,7 +17418,7 @@ function createExtensionDesignTools(store) {
|
|
|
17186
17418
|
};
|
|
17187
17419
|
}
|
|
17188
17420
|
),
|
|
17189
|
-
|
|
17421
|
+
tool17(
|
|
17190
17422
|
"update_extension_design",
|
|
17191
17423
|
"Update an existing extension design. The linked tech assessment cannot be changed.",
|
|
17192
17424
|
{
|
|
@@ -17215,10 +17447,10 @@ function createExtensionDesignTools(store) {
|
|
|
17215
17447
|
}
|
|
17216
17448
|
|
|
17217
17449
|
// src/plugins/builtin/tools/aem-reports.ts
|
|
17218
|
-
import { tool as
|
|
17450
|
+
import { tool as tool18 } from "@anthropic-ai/claude-agent-sdk";
|
|
17219
17451
|
function createAemReportTools(store) {
|
|
17220
17452
|
return [
|
|
17221
|
-
|
|
17453
|
+
tool18(
|
|
17222
17454
|
"generate_extension_portfolio",
|
|
17223
17455
|
"Generate a portfolio view of all use cases with their linked tech assessments and extension designs",
|
|
17224
17456
|
{},
|
|
@@ -17270,7 +17502,7 @@ function createAemReportTools(store) {
|
|
|
17270
17502
|
},
|
|
17271
17503
|
{ annotations: { readOnlyHint: true } }
|
|
17272
17504
|
),
|
|
17273
|
-
|
|
17505
|
+
tool18(
|
|
17274
17506
|
"generate_tech_readiness",
|
|
17275
17507
|
"Generate a BTP technology readiness report showing service coverage and gaps across assessments",
|
|
17276
17508
|
{},
|
|
@@ -17322,7 +17554,7 @@ function createAemReportTools(store) {
|
|
|
17322
17554
|
},
|
|
17323
17555
|
{ annotations: { readOnlyHint: true } }
|
|
17324
17556
|
),
|
|
17325
|
-
|
|
17557
|
+
tool18(
|
|
17326
17558
|
"generate_phase_status",
|
|
17327
17559
|
"Generate a phase progress report showing artifact counts and readiness per AEM phase",
|
|
17328
17560
|
{},
|
|
@@ -17384,11 +17616,11 @@ function createAemReportTools(store) {
|
|
|
17384
17616
|
import * as fs6 from "fs";
|
|
17385
17617
|
import * as path6 from "path";
|
|
17386
17618
|
import * as YAML4 from "yaml";
|
|
17387
|
-
import { tool as
|
|
17619
|
+
import { tool as tool19 } from "@anthropic-ai/claude-agent-sdk";
|
|
17388
17620
|
var PHASES = ["assess-use-case", "assess-technology", "define-solution"];
|
|
17389
17621
|
function createAemPhaseTools(store, marvinDir) {
|
|
17390
17622
|
return [
|
|
17391
|
-
|
|
17623
|
+
tool19(
|
|
17392
17624
|
"get_current_phase",
|
|
17393
17625
|
"Get the current AEM phase from project configuration",
|
|
17394
17626
|
{},
|
|
@@ -17409,7 +17641,7 @@ function createAemPhaseTools(store, marvinDir) {
|
|
|
17409
17641
|
},
|
|
17410
17642
|
{ annotations: { readOnlyHint: true } }
|
|
17411
17643
|
),
|
|
17412
|
-
|
|
17644
|
+
tool19(
|
|
17413
17645
|
"advance_phase",
|
|
17414
17646
|
"Advance to the next AEM phase. Performs soft gate checks and warns if artifacts are incomplete, but does not block.",
|
|
17415
17647
|
{
|
|
@@ -17673,8 +17905,8 @@ function getPluginPromptFragment(plugin, personaId) {
|
|
|
17673
17905
|
}
|
|
17674
17906
|
|
|
17675
17907
|
// src/skills/registry.ts
|
|
17676
|
-
import * as
|
|
17677
|
-
import * as
|
|
17908
|
+
import * as fs8 from "fs";
|
|
17909
|
+
import * as path8 from "path";
|
|
17678
17910
|
import { fileURLToPath } from "url";
|
|
17679
17911
|
import * as YAML5 from "yaml";
|
|
17680
17912
|
import matter2 from "gray-matter";
|
|
@@ -17717,7 +17949,7 @@ Be thorough but concise. Focus on actionable insights.`,
|
|
|
17717
17949
|
};
|
|
17718
17950
|
|
|
17719
17951
|
// src/skills/builtin/jira/tools.ts
|
|
17720
|
-
import { tool as
|
|
17952
|
+
import { tool as tool20 } from "@anthropic-ai/claude-agent-sdk";
|
|
17721
17953
|
|
|
17722
17954
|
// src/skills/builtin/jira/client.ts
|
|
17723
17955
|
var JiraClient = class {
|
|
@@ -17727,8 +17959,8 @@ var JiraClient = class {
|
|
|
17727
17959
|
this.baseUrl = `https://${config2.host}/rest/api/2`;
|
|
17728
17960
|
this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
|
|
17729
17961
|
}
|
|
17730
|
-
async request(
|
|
17731
|
-
const url2 = `${this.baseUrl}${
|
|
17962
|
+
async request(path11, method = "GET", body) {
|
|
17963
|
+
const url2 = `${this.baseUrl}${path11}`;
|
|
17732
17964
|
const headers = {
|
|
17733
17965
|
Authorization: this.authHeader,
|
|
17734
17966
|
"Content-Type": "application/json",
|
|
@@ -17742,7 +17974,7 @@ var JiraClient = class {
|
|
|
17742
17974
|
if (!response.ok) {
|
|
17743
17975
|
const text = await response.text().catch(() => "");
|
|
17744
17976
|
throw new Error(
|
|
17745
|
-
`Jira API error ${response.status} ${method} ${
|
|
17977
|
+
`Jira API error ${response.status} ${method} ${path11}: ${text}`
|
|
17746
17978
|
);
|
|
17747
17979
|
}
|
|
17748
17980
|
if (response.status === 204) return void 0;
|
|
@@ -17826,7 +18058,7 @@ function createJiraTools(store) {
|
|
|
17826
18058
|
const jiraUserConfig = loadUserConfig().jira;
|
|
17827
18059
|
return [
|
|
17828
18060
|
// --- Local read tools ---
|
|
17829
|
-
|
|
18061
|
+
tool20(
|
|
17830
18062
|
"list_jira_issues",
|
|
17831
18063
|
"List locally synced Jira issues (JI-xxx documents), optionally filtered by status or Jira key",
|
|
17832
18064
|
{
|
|
@@ -17854,7 +18086,7 @@ function createJiraTools(store) {
|
|
|
17854
18086
|
},
|
|
17855
18087
|
{ annotations: { readOnlyHint: true } }
|
|
17856
18088
|
),
|
|
17857
|
-
|
|
18089
|
+
tool20(
|
|
17858
18090
|
"get_jira_issue",
|
|
17859
18091
|
"Get the full content of a locally synced Jira issue by local ID (JI-xxx) or Jira key (PROJ-123)",
|
|
17860
18092
|
{
|
|
@@ -17887,7 +18119,7 @@ function createJiraTools(store) {
|
|
|
17887
18119
|
{ annotations: { readOnlyHint: true } }
|
|
17888
18120
|
),
|
|
17889
18121
|
// --- Jira → Local tools ---
|
|
17890
|
-
|
|
18122
|
+
tool20(
|
|
17891
18123
|
"pull_jira_issue",
|
|
17892
18124
|
"Fetch a single Jira issue by key and create/update a local JI-xxx document",
|
|
17893
18125
|
{
|
|
@@ -17934,7 +18166,7 @@ function createJiraTools(store) {
|
|
|
17934
18166
|
};
|
|
17935
18167
|
}
|
|
17936
18168
|
),
|
|
17937
|
-
|
|
18169
|
+
tool20(
|
|
17938
18170
|
"pull_jira_issues_jql",
|
|
17939
18171
|
"Bulk fetch Jira issues via JQL query and create/update local JI-xxx documents",
|
|
17940
18172
|
{
|
|
@@ -17982,7 +18214,7 @@ function createJiraTools(store) {
|
|
|
17982
18214
|
}
|
|
17983
18215
|
),
|
|
17984
18216
|
// --- Local → Jira tools ---
|
|
17985
|
-
|
|
18217
|
+
tool20(
|
|
17986
18218
|
"push_artifact_to_jira",
|
|
17987
18219
|
"Create a Jira issue from any Marvin artifact (D/A/Q/F/E) and create a tracking JI-xxx document",
|
|
17988
18220
|
{
|
|
@@ -18043,7 +18275,7 @@ function createJiraTools(store) {
|
|
|
18043
18275
|
}
|
|
18044
18276
|
),
|
|
18045
18277
|
// --- Bidirectional sync ---
|
|
18046
|
-
|
|
18278
|
+
tool20(
|
|
18047
18279
|
"sync_jira_issue",
|
|
18048
18280
|
"Bidirectional sync: push local title/description to Jira, pull latest status/assignee/labels back",
|
|
18049
18281
|
{
|
|
@@ -18084,7 +18316,7 @@ function createJiraTools(store) {
|
|
|
18084
18316
|
}
|
|
18085
18317
|
),
|
|
18086
18318
|
// --- Local link tool ---
|
|
18087
|
-
|
|
18319
|
+
tool20(
|
|
18088
18320
|
"link_artifact_to_jira",
|
|
18089
18321
|
"Add a Marvin artifact ID to a JI-xxx document's linkedArtifacts field",
|
|
18090
18322
|
{
|
|
@@ -18172,13 +18404,13 @@ var jiraSkill = {
|
|
|
18172
18404
|
**Available tools:**
|
|
18173
18405
|
- \`list_jira_issues\` / \`get_jira_issue\` \u2014 browse locally synced Jira issues
|
|
18174
18406
|
- \`pull_jira_issue\` / \`pull_jira_issues_jql\` \u2014 import issues from Jira by key or JQL query
|
|
18175
|
-
- \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, action, epic, etc.)
|
|
18407
|
+
- \`push_artifact_to_jira\` \u2014 create a Jira issue from a Marvin artifact (decision, action, epic, task, etc.)
|
|
18176
18408
|
- \`sync_jira_issue\` \u2014 bidirectional sync of a local JI-xxx with Jira
|
|
18177
18409
|
- \`link_artifact_to_jira\` \u2014 link a Marvin artifact to an existing JI-xxx
|
|
18178
18410
|
|
|
18179
18411
|
**As Tech Lead, use Jira integration to:**
|
|
18180
18412
|
- Pull technical issues and bugs for sprint planning and estimation
|
|
18181
|
-
- Push epics and technical decisions to Jira for cross-team visibility
|
|
18413
|
+
- Push epics, tasks, and technical decisions to Jira for cross-team visibility
|
|
18182
18414
|
- Bidirectional sync to keep local governance and Jira in alignment
|
|
18183
18415
|
- Use JQL queries to track technical debt (e.g. \`labels = "tech-debt" AND status != "Done"\`)`,
|
|
18184
18416
|
"delivery-manager": `You have the **Jira Integration** skill. You can pull issues from Jira and push Marvin artifacts to Jira.
|
|
@@ -18192,72 +18424,476 @@ var jiraSkill = {
|
|
|
18192
18424
|
|
|
18193
18425
|
**As Delivery Manager, use Jira integration to:**
|
|
18194
18426
|
- Pull sprint issues for tracking progress and blockers
|
|
18195
|
-
- Push actions and
|
|
18427
|
+
- Push actions, decisions, and tasks to Jira for stakeholder visibility
|
|
18196
18428
|
- Use JQL queries for reporting (e.g. \`sprint in openSprints() AND assignee = currentUser()\`)
|
|
18197
18429
|
- Sync status between Marvin governance items and Jira issues`
|
|
18198
18430
|
}
|
|
18199
18431
|
};
|
|
18200
18432
|
|
|
18201
|
-
// src/skills/
|
|
18202
|
-
|
|
18203
|
-
|
|
18204
|
-
|
|
18433
|
+
// src/skills/builtin/prd-generator/tools.ts
|
|
18434
|
+
import * as fs7 from "fs";
|
|
18435
|
+
import * as path7 from "path";
|
|
18436
|
+
import { tool as tool21 } from "@anthropic-ai/claude-agent-sdk";
|
|
18437
|
+
var PRIORITY_ORDER2 = {
|
|
18438
|
+
critical: 0,
|
|
18439
|
+
high: 1,
|
|
18440
|
+
medium: 2,
|
|
18441
|
+
low: 3
|
|
18205
18442
|
};
|
|
18206
|
-
function
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
|
|
18210
|
-
|
|
18211
|
-
const
|
|
18212
|
-
|
|
18213
|
-
|
|
18214
|
-
|
|
18215
|
-
|
|
18216
|
-
|
|
18217
|
-
|
|
18218
|
-
|
|
18219
|
-
|
|
18220
|
-
|
|
18221
|
-
const
|
|
18222
|
-
|
|
18223
|
-
|
|
18224
|
-
|
|
18225
|
-
const
|
|
18226
|
-
|
|
18227
|
-
|
|
18228
|
-
|
|
18229
|
-
|
|
18230
|
-
|
|
18231
|
-
|
|
18232
|
-
|
|
18233
|
-
|
|
18443
|
+
function priorityRank2(p) {
|
|
18444
|
+
return PRIORITY_ORDER2[p ?? ""] ?? 99;
|
|
18445
|
+
}
|
|
18446
|
+
function gatherContext(store, focusFeature, includeDecisions = true, includeQuestions = true) {
|
|
18447
|
+
const allFeatures = store.list({ type: "feature" });
|
|
18448
|
+
const allEpics = store.list({ type: "epic" });
|
|
18449
|
+
const allTasks = store.list({ type: "task" });
|
|
18450
|
+
const allDecisions = includeDecisions ? store.list({ type: "decision" }) : [];
|
|
18451
|
+
const allQuestions = includeQuestions ? store.list({ type: "question" }) : [];
|
|
18452
|
+
const allActions = store.list({ type: "action" });
|
|
18453
|
+
let features = allFeatures;
|
|
18454
|
+
let epics = allEpics;
|
|
18455
|
+
let tasks = allTasks;
|
|
18456
|
+
if (focusFeature) {
|
|
18457
|
+
features = features.filter((f) => f.frontmatter.id === focusFeature);
|
|
18458
|
+
const featureIds = new Set(features.map((f) => f.frontmatter.id));
|
|
18459
|
+
epics = epics.filter(
|
|
18460
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).some((id) => featureIds.has(id))
|
|
18461
|
+
);
|
|
18462
|
+
const epicIds2 = new Set(epics.map((e) => e.frontmatter.id));
|
|
18463
|
+
tasks = tasks.filter(
|
|
18464
|
+
(t) => normalizeLinkedEpics(t.frontmatter.linkedEpic).some((id) => epicIds2.has(id))
|
|
18465
|
+
);
|
|
18466
|
+
}
|
|
18467
|
+
const featuresByStatus = {};
|
|
18468
|
+
for (const f of features) {
|
|
18469
|
+
featuresByStatus[f.frontmatter.status] = (featuresByStatus[f.frontmatter.status] ?? 0) + 1;
|
|
18470
|
+
}
|
|
18471
|
+
const epicsByStatus = {};
|
|
18472
|
+
for (const e of epics) {
|
|
18473
|
+
epicsByStatus[e.frontmatter.status] = (epicsByStatus[e.frontmatter.status] ?? 0) + 1;
|
|
18474
|
+
}
|
|
18475
|
+
const epicIds = new Set(epics.map((e) => e.frontmatter.id));
|
|
18476
|
+
return {
|
|
18477
|
+
features: features.sort((a, b) => priorityRank2(a.frontmatter.priority) - priorityRank2(b.frontmatter.priority)).map((f) => ({
|
|
18478
|
+
id: f.frontmatter.id,
|
|
18479
|
+
title: f.frontmatter.title,
|
|
18480
|
+
status: f.frontmatter.status,
|
|
18481
|
+
priority: f.frontmatter.priority ?? "medium",
|
|
18482
|
+
content: f.content,
|
|
18483
|
+
linkedEpicCount: epics.filter(
|
|
18484
|
+
(e) => normalizeLinkedFeatures(e.frontmatter.linkedFeature).includes(f.frontmatter.id)
|
|
18485
|
+
).length
|
|
18486
|
+
})),
|
|
18487
|
+
epics: epics.map((e) => ({
|
|
18488
|
+
id: e.frontmatter.id,
|
|
18489
|
+
title: e.frontmatter.title,
|
|
18490
|
+
status: e.frontmatter.status,
|
|
18491
|
+
linkedFeature: normalizeLinkedFeatures(e.frontmatter.linkedFeature),
|
|
18492
|
+
targetDate: e.frontmatter.targetDate ?? null,
|
|
18493
|
+
estimatedEffort: e.frontmatter.estimatedEffort ?? null,
|
|
18494
|
+
content: e.content,
|
|
18495
|
+
linkedTaskCount: tasks.filter(
|
|
18496
|
+
(t) => normalizeLinkedEpics(t.frontmatter.linkedEpic).includes(e.frontmatter.id)
|
|
18497
|
+
).length
|
|
18498
|
+
})),
|
|
18499
|
+
tasks: tasks.map((t) => ({
|
|
18500
|
+
id: t.frontmatter.id,
|
|
18501
|
+
title: t.frontmatter.title,
|
|
18502
|
+
status: t.frontmatter.status,
|
|
18503
|
+
linkedEpic: normalizeLinkedEpics(t.frontmatter.linkedEpic),
|
|
18504
|
+
acceptanceCriteria: t.frontmatter.acceptanceCriteria ?? null,
|
|
18505
|
+
technicalNotes: t.frontmatter.technicalNotes ?? null,
|
|
18506
|
+
complexity: t.frontmatter.complexity ?? null,
|
|
18507
|
+
estimatedPoints: t.frontmatter.estimatedPoints ?? null,
|
|
18508
|
+
priority: t.frontmatter.priority ?? null
|
|
18509
|
+
})),
|
|
18510
|
+
decisions: allDecisions.map((d) => ({
|
|
18511
|
+
id: d.frontmatter.id,
|
|
18512
|
+
title: d.frontmatter.title,
|
|
18513
|
+
status: d.frontmatter.status,
|
|
18514
|
+
content: d.content
|
|
18515
|
+
})),
|
|
18516
|
+
questions: allQuestions.map((q) => ({
|
|
18517
|
+
id: q.frontmatter.id,
|
|
18518
|
+
title: q.frontmatter.title,
|
|
18519
|
+
status: q.frontmatter.status,
|
|
18520
|
+
content: q.content
|
|
18521
|
+
})),
|
|
18522
|
+
actions: allActions.filter((a) => {
|
|
18523
|
+
if (!focusFeature) return true;
|
|
18524
|
+
const tags = a.frontmatter.tags ?? [];
|
|
18525
|
+
return tags.some((t) => t.startsWith("epic:") && epicIds.has(t.replace("epic:", "")));
|
|
18526
|
+
}).map((a) => ({
|
|
18527
|
+
id: a.frontmatter.id,
|
|
18528
|
+
title: a.frontmatter.title,
|
|
18529
|
+
status: a.frontmatter.status,
|
|
18530
|
+
owner: a.frontmatter.owner ?? null,
|
|
18531
|
+
priority: a.frontmatter.priority ?? null,
|
|
18532
|
+
dueDate: a.frontmatter.dueDate ?? null
|
|
18533
|
+
})),
|
|
18534
|
+
summary: {
|
|
18535
|
+
totalFeatures: features.length,
|
|
18536
|
+
totalEpics: epics.length,
|
|
18537
|
+
totalTasks: tasks.length,
|
|
18538
|
+
featuresByStatus,
|
|
18539
|
+
epicsByStatus
|
|
18540
|
+
}
|
|
18541
|
+
};
|
|
18542
|
+
}
|
|
18543
|
+
function generateTaskMasterPrd(title, ctx, projectOverview) {
|
|
18544
|
+
const lines = [];
|
|
18545
|
+
lines.push(`# ${title}`);
|
|
18546
|
+
lines.push("");
|
|
18547
|
+
lines.push("## Project Overview");
|
|
18548
|
+
if (projectOverview) {
|
|
18549
|
+
lines.push(projectOverview);
|
|
18550
|
+
} else if (ctx.features.length > 0) {
|
|
18551
|
+
lines.push(`This project encompasses ${ctx.features.length} feature(s) spanning ${ctx.epics.length} epic(s) and ${ctx.tasks.length} implementation task(s).`);
|
|
18552
|
+
}
|
|
18553
|
+
lines.push("");
|
|
18554
|
+
lines.push("## Goals");
|
|
18555
|
+
for (const f of ctx.features) {
|
|
18556
|
+
lines.push(`- **${f.title}** (${f.id}, Priority: ${f.priority}) \u2014 ${f.status}`);
|
|
18557
|
+
}
|
|
18558
|
+
lines.push("");
|
|
18559
|
+
lines.push("## Features and Requirements");
|
|
18560
|
+
lines.push("");
|
|
18561
|
+
for (const feature of ctx.features) {
|
|
18562
|
+
lines.push(`### ${feature.title} (${feature.id}) \u2014 Priority: ${feature.priority}`);
|
|
18563
|
+
lines.push("");
|
|
18564
|
+
if (feature.content) {
|
|
18565
|
+
lines.push(feature.content);
|
|
18566
|
+
lines.push("");
|
|
18567
|
+
}
|
|
18568
|
+
const featureEpics = ctx.epics.filter((e) => e.linkedFeature.includes(feature.id));
|
|
18569
|
+
if (featureEpics.length > 0) {
|
|
18570
|
+
lines.push("#### User Stories / Epics");
|
|
18571
|
+
lines.push("");
|
|
18572
|
+
for (const epic of featureEpics) {
|
|
18573
|
+
const effort = epic.estimatedEffort ? `, Effort: ${epic.estimatedEffort}` : "";
|
|
18574
|
+
lines.push(`- **${epic.id}: ${epic.title}** \u2014 Status: ${epic.status}${effort}`);
|
|
18575
|
+
if (epic.content) {
|
|
18576
|
+
lines.push(` ${epic.content.split("\n")[0]}`);
|
|
18577
|
+
}
|
|
18578
|
+
const epicTasks = ctx.tasks.filter((t) => t.linkedEpic.includes(epic.id));
|
|
18579
|
+
if (epicTasks.length > 0) {
|
|
18580
|
+
lines.push("");
|
|
18581
|
+
lines.push("#### Implementation Tasks");
|
|
18582
|
+
lines.push("");
|
|
18583
|
+
for (const task of epicTasks) {
|
|
18584
|
+
const complexity = task.complexity ? `, Complexity: ${task.complexity}` : "";
|
|
18585
|
+
const points = task.estimatedPoints != null ? `, Points: ${task.estimatedPoints}` : "";
|
|
18586
|
+
lines.push(`- **${task.id}: ${task.title}**${complexity}${points}`);
|
|
18587
|
+
if (task.acceptanceCriteria) {
|
|
18588
|
+
lines.push(` Acceptance Criteria: ${task.acceptanceCriteria}`);
|
|
18589
|
+
}
|
|
18234
18590
|
}
|
|
18235
18591
|
}
|
|
18236
|
-
} catch {
|
|
18237
18592
|
}
|
|
18593
|
+
lines.push("");
|
|
18238
18594
|
}
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18244
|
-
|
|
18245
|
-
|
|
18595
|
+
}
|
|
18596
|
+
const approvedDecisions = ctx.decisions.filter((d) => d.status === "approved" || d.status === "accepted");
|
|
18597
|
+
const openQuestions = ctx.questions.filter((q) => q.status === "open");
|
|
18598
|
+
const technicalNotes = ctx.tasks.filter((t) => t.technicalNotes).map((t) => `- **${t.id}**: ${t.technicalNotes}`);
|
|
18599
|
+
if (approvedDecisions.length > 0 || openQuestions.length > 0 || technicalNotes.length > 0) {
|
|
18600
|
+
lines.push("## Technical Considerations");
|
|
18601
|
+
lines.push("");
|
|
18602
|
+
if (approvedDecisions.length > 0) {
|
|
18603
|
+
lines.push("### Key Decisions");
|
|
18604
|
+
for (const d of approvedDecisions) {
|
|
18605
|
+
lines.push(`- **${d.id}: ${d.title}** \u2014 ${d.content.split("\n")[0]}`);
|
|
18246
18606
|
}
|
|
18607
|
+
lines.push("");
|
|
18608
|
+
}
|
|
18609
|
+
if (technicalNotes.length > 0) {
|
|
18610
|
+
lines.push("### Technical Notes");
|
|
18611
|
+
for (const note of technicalNotes) {
|
|
18612
|
+
lines.push(note);
|
|
18613
|
+
}
|
|
18614
|
+
lines.push("");
|
|
18615
|
+
}
|
|
18616
|
+
if (openQuestions.length > 0) {
|
|
18617
|
+
lines.push("### Open Questions");
|
|
18618
|
+
for (const q of openQuestions) {
|
|
18619
|
+
lines.push(`- **${q.id}: ${q.title}** \u2014 ${q.content.split("\n")[0]}`);
|
|
18620
|
+
}
|
|
18621
|
+
lines.push("");
|
|
18247
18622
|
}
|
|
18248
|
-
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
|
|
18252
|
-
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
|
|
18258
|
-
};
|
|
18259
|
-
|
|
18260
|
-
|
|
18623
|
+
}
|
|
18624
|
+
lines.push("## Implementation Priorities");
|
|
18625
|
+
lines.push("");
|
|
18626
|
+
let priorityIdx = 1;
|
|
18627
|
+
for (const feature of ctx.features) {
|
|
18628
|
+
const featureEpics = ctx.epics.filter((e) => e.linkedFeature.includes(feature.id)).sort((a, b) => {
|
|
18629
|
+
const statusOrder = { "in-progress": 0, planned: 1, done: 2 };
|
|
18630
|
+
return (statusOrder[a.status] ?? 99) - (statusOrder[b.status] ?? 99);
|
|
18631
|
+
});
|
|
18632
|
+
if (featureEpics.length === 0) continue;
|
|
18633
|
+
lines.push(`${priorityIdx}. **${feature.title}** (${feature.priority})`);
|
|
18634
|
+
for (const epic of featureEpics) {
|
|
18635
|
+
const epicTasks = ctx.tasks.filter((t) => t.linkedEpic.includes(epic.id));
|
|
18636
|
+
lines.push(` - ${epic.id}: ${epic.title} (${epic.status}) \u2014 ${epicTasks.length} task(s)`);
|
|
18637
|
+
}
|
|
18638
|
+
priorityIdx++;
|
|
18639
|
+
}
|
|
18640
|
+
lines.push("");
|
|
18641
|
+
return lines.join("\n");
|
|
18642
|
+
}
|
|
18643
|
+
function generateClaudeCodePrd(title, ctx, projectOverview) {
|
|
18644
|
+
const lines = [];
|
|
18645
|
+
lines.push(`# ${title}`);
|
|
18646
|
+
lines.push("");
|
|
18647
|
+
lines.push("## Overview");
|
|
18648
|
+
if (projectOverview) {
|
|
18649
|
+
lines.push(projectOverview);
|
|
18650
|
+
} else if (ctx.features.length > 0) {
|
|
18651
|
+
lines.push(`This project encompasses ${ctx.features.length} feature(s) spanning ${ctx.epics.length} epic(s) and ${ctx.tasks.length} implementation task(s).`);
|
|
18652
|
+
}
|
|
18653
|
+
lines.push("");
|
|
18654
|
+
const approvedDecisions = ctx.decisions.filter((d) => d.status === "approved" || d.status === "accepted");
|
|
18655
|
+
if (approvedDecisions.length > 0) {
|
|
18656
|
+
lines.push("## Architecture & Technical Decisions");
|
|
18657
|
+
lines.push("");
|
|
18658
|
+
for (const d of approvedDecisions) {
|
|
18659
|
+
lines.push(`### ${d.id}: ${d.title}`);
|
|
18660
|
+
lines.push(d.content);
|
|
18661
|
+
lines.push("");
|
|
18662
|
+
}
|
|
18663
|
+
}
|
|
18664
|
+
lines.push("## Implementation Plan");
|
|
18665
|
+
lines.push("");
|
|
18666
|
+
const priorityGroups = {};
|
|
18667
|
+
for (const f of ctx.features) {
|
|
18668
|
+
const group = f.priority === "critical" || f.priority === "high" ? "Phase 1: High Priority" : "Phase 2: Medium & Low Priority";
|
|
18669
|
+
if (!priorityGroups[group]) priorityGroups[group] = [];
|
|
18670
|
+
priorityGroups[group].push(f);
|
|
18671
|
+
}
|
|
18672
|
+
for (const [phase, features] of Object.entries(priorityGroups)) {
|
|
18673
|
+
lines.push(`### ${phase}`);
|
|
18674
|
+
lines.push("");
|
|
18675
|
+
for (const feature of features) {
|
|
18676
|
+
const featureEpics = ctx.epics.filter((e) => e.linkedFeature.includes(feature.id));
|
|
18677
|
+
for (const epic of featureEpics) {
|
|
18678
|
+
lines.push(`- [ ] ${epic.id}: ${epic.title}`);
|
|
18679
|
+
const epicTasks = ctx.tasks.filter((t) => t.linkedEpic.includes(epic.id));
|
|
18680
|
+
for (const task of epicTasks) {
|
|
18681
|
+
const complexity = task.complexity ? `complexity: ${task.complexity}` : "";
|
|
18682
|
+
const points = task.estimatedPoints != null ? `points: ${task.estimatedPoints}` : "";
|
|
18683
|
+
const meta3 = [complexity, points].filter(Boolean).join(", ");
|
|
18684
|
+
lines.push(` - [ ] ${task.id}: ${task.title}${meta3 ? ` (${meta3})` : ""}`);
|
|
18685
|
+
if (task.acceptanceCriteria) {
|
|
18686
|
+
lines.push(` - Acceptance: ${task.acceptanceCriteria}`);
|
|
18687
|
+
}
|
|
18688
|
+
if (task.technicalNotes) {
|
|
18689
|
+
lines.push(` - Notes: ${task.technicalNotes}`);
|
|
18690
|
+
}
|
|
18691
|
+
}
|
|
18692
|
+
}
|
|
18693
|
+
}
|
|
18694
|
+
lines.push("");
|
|
18695
|
+
}
|
|
18696
|
+
const openQuestions = ctx.questions.filter((q) => q.status === "open");
|
|
18697
|
+
if (openQuestions.length > 0) {
|
|
18698
|
+
lines.push("## Open Questions");
|
|
18699
|
+
lines.push("");
|
|
18700
|
+
for (const q of openQuestions) {
|
|
18701
|
+
lines.push(`- **${q.id}: ${q.title}** \u2014 ${q.content.split("\n")[0]}`);
|
|
18702
|
+
}
|
|
18703
|
+
lines.push("");
|
|
18704
|
+
}
|
|
18705
|
+
return lines.join("\n");
|
|
18706
|
+
}
|
|
18707
|
+
function createPrdTools(store) {
|
|
18708
|
+
return [
|
|
18709
|
+
tool21(
|
|
18710
|
+
"gather_prd_context",
|
|
18711
|
+
"Aggregate all governance artifacts (features, epics, tasks, decisions, questions, actions) into structured JSON for PRD generation",
|
|
18712
|
+
{
|
|
18713
|
+
focusFeature: external_exports.string().optional().describe("Filter context to a specific feature ID (e.g. 'F-001')"),
|
|
18714
|
+
includeDecisions: external_exports.boolean().optional().describe("Include decisions in context (default: true)"),
|
|
18715
|
+
includeQuestions: external_exports.boolean().optional().describe("Include questions in context (default: true)")
|
|
18716
|
+
},
|
|
18717
|
+
async (args) => {
|
|
18718
|
+
const ctx = gatherContext(store, args.focusFeature, args.includeDecisions ?? true, args.includeQuestions ?? true);
|
|
18719
|
+
return {
|
|
18720
|
+
content: [{ type: "text", text: JSON.stringify(ctx, null, 2) }]
|
|
18721
|
+
};
|
|
18722
|
+
},
|
|
18723
|
+
{ annotations: { readOnlyHint: true } }
|
|
18724
|
+
),
|
|
18725
|
+
tool21(
|
|
18726
|
+
"generate_prd",
|
|
18727
|
+
"Generate a PRD document from governance artifacts and save it as a PRD-xxx document",
|
|
18728
|
+
{
|
|
18729
|
+
title: external_exports.string().describe("PRD title"),
|
|
18730
|
+
format: external_exports.enum(["taskmaster", "claude-code"]).describe("Output format: 'taskmaster' for Claude TaskMaster parse_prd, 'claude-code' for Claude Code consumption"),
|
|
18731
|
+
projectOverview: external_exports.string().optional().describe("Project overview text (synthesized from features if not provided)"),
|
|
18732
|
+
focusFeature: external_exports.string().optional().describe("Focus on a specific feature ID (e.g. 'F-001')"),
|
|
18733
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Tags for the PRD document")
|
|
18734
|
+
},
|
|
18735
|
+
async (args) => {
|
|
18736
|
+
const ctx = gatherContext(store, args.focusFeature);
|
|
18737
|
+
const prdContent = args.format === "taskmaster" ? generateTaskMasterPrd(args.title, ctx, args.projectOverview) : generateClaudeCodePrd(args.title, ctx, args.projectOverview);
|
|
18738
|
+
const frontmatter = {
|
|
18739
|
+
title: args.title,
|
|
18740
|
+
status: "draft",
|
|
18741
|
+
format: args.format
|
|
18742
|
+
};
|
|
18743
|
+
if (args.focusFeature) frontmatter.focusFeature = args.focusFeature;
|
|
18744
|
+
if (args.tags) frontmatter.tags = args.tags;
|
|
18745
|
+
const doc = store.create("prd", frontmatter, prdContent);
|
|
18746
|
+
return {
|
|
18747
|
+
content: [
|
|
18748
|
+
{
|
|
18749
|
+
type: "text",
|
|
18750
|
+
text: `Generated PRD ${doc.frontmatter.id}: "${args.title}" (format: ${args.format}, ${ctx.summary.totalFeatures} features, ${ctx.summary.totalEpics} epics, ${ctx.summary.totalTasks} tasks)`
|
|
18751
|
+
}
|
|
18752
|
+
]
|
|
18753
|
+
};
|
|
18754
|
+
}
|
|
18755
|
+
),
|
|
18756
|
+
tool21(
|
|
18757
|
+
"export_prd",
|
|
18758
|
+
"Export a PRD document to a file path for external consumption (e.g. by Claude TaskMaster or Claude Code)",
|
|
18759
|
+
{
|
|
18760
|
+
prdId: external_exports.string().describe("PRD document ID (e.g. 'PRD-001')"),
|
|
18761
|
+
outputPath: external_exports.string().describe("File path to write the PRD content to")
|
|
18762
|
+
},
|
|
18763
|
+
async (args) => {
|
|
18764
|
+
const doc = store.get(args.prdId);
|
|
18765
|
+
if (!doc) {
|
|
18766
|
+
return {
|
|
18767
|
+
content: [{ type: "text", text: `PRD ${args.prdId} not found` }],
|
|
18768
|
+
isError: true
|
|
18769
|
+
};
|
|
18770
|
+
}
|
|
18771
|
+
const outputDir = path7.dirname(args.outputPath);
|
|
18772
|
+
fs7.mkdirSync(outputDir, { recursive: true });
|
|
18773
|
+
fs7.writeFileSync(args.outputPath, doc.content, "utf-8");
|
|
18774
|
+
return {
|
|
18775
|
+
content: [
|
|
18776
|
+
{
|
|
18777
|
+
type: "text",
|
|
18778
|
+
text: `Exported PRD ${args.prdId} to ${args.outputPath}`
|
|
18779
|
+
}
|
|
18780
|
+
]
|
|
18781
|
+
};
|
|
18782
|
+
}
|
|
18783
|
+
)
|
|
18784
|
+
];
|
|
18785
|
+
}
|
|
18786
|
+
|
|
18787
|
+
// src/skills/builtin/prd-generator/index.ts
|
|
18788
|
+
var prdGeneratorSkill = {
|
|
18789
|
+
id: "prd-generator",
|
|
18790
|
+
name: "PRD Generator",
|
|
18791
|
+
description: "Generate PRDs from governance artifacts for TaskMaster or Claude Code",
|
|
18792
|
+
version: "1.0.0",
|
|
18793
|
+
format: "builtin-ts",
|
|
18794
|
+
documentTypeRegistrations: [
|
|
18795
|
+
{ type: "prd", dirName: "prds", idPrefix: "PRD" }
|
|
18796
|
+
],
|
|
18797
|
+
tools: (store) => createPrdTools(store),
|
|
18798
|
+
promptFragments: {
|
|
18799
|
+
"tech-lead": `You have the **PRD Generator** skill. You can generate Product Requirements Documents from governance artifacts.
|
|
18800
|
+
|
|
18801
|
+
**Available tools:**
|
|
18802
|
+
- \`gather_prd_context\` \u2014 aggregate features, epics, tasks, decisions, questions, and actions into structured JSON for analysis
|
|
18803
|
+
- \`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)
|
|
18804
|
+
- \`export_prd\` \u2014 export a PRD document to a file path for external use
|
|
18805
|
+
|
|
18806
|
+
**As Tech Lead, use PRD generation to:**
|
|
18807
|
+
- Create comprehensive PRDs that capture the full governance context
|
|
18808
|
+
- Export TaskMaster-format PRDs for automated task breakdown via \`parse_prd\`
|
|
18809
|
+
- Export Claude Code-format PRDs as implementation plans with checklists
|
|
18810
|
+
- Focus PRDs on specific features using the focusFeature parameter`,
|
|
18811
|
+
"delivery-manager": `You have the **PRD Generator** skill. You can generate Product Requirements Documents from governance artifacts.
|
|
18812
|
+
|
|
18813
|
+
**Available tools:**
|
|
18814
|
+
- \`gather_prd_context\` \u2014 aggregate all governance artifacts into structured JSON for review
|
|
18815
|
+
- \`generate_prd\` \u2014 generate a formatted PRD document (taskmaster or claude-code format)
|
|
18816
|
+
- \`export_prd\` \u2014 export a PRD to a file path
|
|
18817
|
+
|
|
18818
|
+
**As Delivery Manager, use PRD generation to:**
|
|
18819
|
+
- Generate PRDs for stakeholder communication and project documentation
|
|
18820
|
+
- Review aggregated project context before sprint planning
|
|
18821
|
+
- Export PRDs to share with external teams or tools`,
|
|
18822
|
+
"product-owner": `You have the **PRD Generator** skill. You can generate Product Requirements Documents from governance artifacts.
|
|
18823
|
+
|
|
18824
|
+
**Available tools:**
|
|
18825
|
+
- \`gather_prd_context\` \u2014 aggregate features, epics, tasks, and decisions into structured JSON
|
|
18826
|
+
- \`generate_prd\` \u2014 generate a formatted PRD document
|
|
18827
|
+
- \`export_prd\` \u2014 export a PRD to a file path
|
|
18828
|
+
|
|
18829
|
+
**As Product Owner, use PRD generation to:**
|
|
18830
|
+
- Generate PRDs that capture feature requirements and priorities
|
|
18831
|
+
- Review the complete governance context for product planning
|
|
18832
|
+
- Export PRDs for stakeholder review and sign-off`
|
|
18833
|
+
}
|
|
18834
|
+
};
|
|
18835
|
+
|
|
18836
|
+
// src/skills/registry.ts
|
|
18837
|
+
var BUILTIN_SKILLS = {
|
|
18838
|
+
"governance-review": governanceReviewSkill,
|
|
18839
|
+
"jira": jiraSkill,
|
|
18840
|
+
"prd-generator": prdGeneratorSkill
|
|
18841
|
+
};
|
|
18842
|
+
function getBuiltinSkillsDir() {
|
|
18843
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
18844
|
+
return path8.join(path8.dirname(thisFile), "builtin");
|
|
18845
|
+
}
|
|
18846
|
+
function loadSkillFromDirectory(dirPath) {
|
|
18847
|
+
const skillMdPath = path8.join(dirPath, "SKILL.md");
|
|
18848
|
+
if (!fs8.existsSync(skillMdPath)) return void 0;
|
|
18849
|
+
try {
|
|
18850
|
+
const raw = fs8.readFileSync(skillMdPath, "utf-8");
|
|
18851
|
+
const { data, content } = matter2(raw);
|
|
18852
|
+
if (!data.name || !data.description) return void 0;
|
|
18853
|
+
const metadata = data.metadata ?? {};
|
|
18854
|
+
const version2 = metadata.version ?? "1.0.0";
|
|
18855
|
+
const personas = metadata.personas;
|
|
18856
|
+
const promptFragments = {};
|
|
18857
|
+
const wildcardPrompt = content.trim();
|
|
18858
|
+
if (wildcardPrompt) {
|
|
18859
|
+
promptFragments["*"] = wildcardPrompt;
|
|
18860
|
+
}
|
|
18861
|
+
const personasDir = path8.join(dirPath, "personas");
|
|
18862
|
+
if (fs8.existsSync(personasDir)) {
|
|
18863
|
+
try {
|
|
18864
|
+
for (const file2 of fs8.readdirSync(personasDir)) {
|
|
18865
|
+
if (!file2.endsWith(".md")) continue;
|
|
18866
|
+
const personaId = file2.replace(/\.md$/, "");
|
|
18867
|
+
const personaPrompt = fs8.readFileSync(path8.join(personasDir, file2), "utf-8").trim();
|
|
18868
|
+
if (personaPrompt) {
|
|
18869
|
+
promptFragments[personaId] = personaPrompt;
|
|
18870
|
+
}
|
|
18871
|
+
}
|
|
18872
|
+
} catch {
|
|
18873
|
+
}
|
|
18874
|
+
}
|
|
18875
|
+
let actions;
|
|
18876
|
+
const actionsPath = path8.join(dirPath, "actions.yaml");
|
|
18877
|
+
if (fs8.existsSync(actionsPath)) {
|
|
18878
|
+
try {
|
|
18879
|
+
const actionsRaw = fs8.readFileSync(actionsPath, "utf-8");
|
|
18880
|
+
actions = YAML5.parse(actionsRaw);
|
|
18881
|
+
} catch {
|
|
18882
|
+
}
|
|
18883
|
+
}
|
|
18884
|
+
return {
|
|
18885
|
+
id: data.name,
|
|
18886
|
+
name: data.name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
18887
|
+
description: data.description,
|
|
18888
|
+
version: version2,
|
|
18889
|
+
format: "skill-md",
|
|
18890
|
+
dirPath,
|
|
18891
|
+
personas,
|
|
18892
|
+
promptFragments: Object.keys(promptFragments).length > 0 ? promptFragments : void 0,
|
|
18893
|
+
actions
|
|
18894
|
+
};
|
|
18895
|
+
} catch {
|
|
18896
|
+
return void 0;
|
|
18261
18897
|
}
|
|
18262
18898
|
}
|
|
18263
18899
|
function loadAllSkills(marvinDir) {
|
|
@@ -18267,10 +18903,10 @@ function loadAllSkills(marvinDir) {
|
|
|
18267
18903
|
}
|
|
18268
18904
|
try {
|
|
18269
18905
|
const builtinDir = getBuiltinSkillsDir();
|
|
18270
|
-
if (
|
|
18271
|
-
for (const entry of
|
|
18272
|
-
const entryPath =
|
|
18273
|
-
if (!
|
|
18906
|
+
if (fs8.existsSync(builtinDir)) {
|
|
18907
|
+
for (const entry of fs8.readdirSync(builtinDir)) {
|
|
18908
|
+
const entryPath = path8.join(builtinDir, entry);
|
|
18909
|
+
if (!fs8.statSync(entryPath).isDirectory()) continue;
|
|
18274
18910
|
if (skills.has(entry)) continue;
|
|
18275
18911
|
const skill = loadSkillFromDirectory(entryPath);
|
|
18276
18912
|
if (skill) skills.set(skill.id, skill);
|
|
@@ -18279,18 +18915,18 @@ function loadAllSkills(marvinDir) {
|
|
|
18279
18915
|
} catch {
|
|
18280
18916
|
}
|
|
18281
18917
|
if (marvinDir) {
|
|
18282
|
-
const skillsDir =
|
|
18283
|
-
if (
|
|
18918
|
+
const skillsDir = path8.join(marvinDir, "skills");
|
|
18919
|
+
if (fs8.existsSync(skillsDir)) {
|
|
18284
18920
|
let entries;
|
|
18285
18921
|
try {
|
|
18286
|
-
entries =
|
|
18922
|
+
entries = fs8.readdirSync(skillsDir);
|
|
18287
18923
|
} catch {
|
|
18288
18924
|
entries = [];
|
|
18289
18925
|
}
|
|
18290
18926
|
for (const entry of entries) {
|
|
18291
|
-
const entryPath =
|
|
18927
|
+
const entryPath = path8.join(skillsDir, entry);
|
|
18292
18928
|
try {
|
|
18293
|
-
if (
|
|
18929
|
+
if (fs8.statSync(entryPath).isDirectory()) {
|
|
18294
18930
|
const skill = loadSkillFromDirectory(entryPath);
|
|
18295
18931
|
if (skill) skills.set(skill.id, skill);
|
|
18296
18932
|
continue;
|
|
@@ -18300,7 +18936,7 @@ function loadAllSkills(marvinDir) {
|
|
|
18300
18936
|
}
|
|
18301
18937
|
if (!entry.endsWith(".yaml") && !entry.endsWith(".yml")) continue;
|
|
18302
18938
|
try {
|
|
18303
|
-
const raw =
|
|
18939
|
+
const raw = fs8.readFileSync(entryPath, "utf-8");
|
|
18304
18940
|
const parsed = YAML5.parse(raw);
|
|
18305
18941
|
if (!parsed?.id || !parsed?.name || !parsed?.version) continue;
|
|
18306
18942
|
const skill = {
|
|
@@ -18368,7 +19004,7 @@ ${fragment}`);
|
|
|
18368
19004
|
}
|
|
18369
19005
|
|
|
18370
19006
|
// src/skills/action-tools.ts
|
|
18371
|
-
import { tool as
|
|
19007
|
+
import { tool as tool23 } from "@anthropic-ai/claude-agent-sdk";
|
|
18372
19008
|
|
|
18373
19009
|
// src/skills/action-runner.ts
|
|
18374
19010
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -18380,7 +19016,7 @@ import {
|
|
|
18380
19016
|
|
|
18381
19017
|
// src/agent/tools/web.ts
|
|
18382
19018
|
import * as http2 from "http";
|
|
18383
|
-
import { tool as
|
|
19019
|
+
import { tool as tool22 } from "@anthropic-ai/claude-agent-sdk";
|
|
18384
19020
|
|
|
18385
19021
|
// src/web/data.ts
|
|
18386
19022
|
function getOverviewData(store) {
|
|
@@ -18616,6 +19252,7 @@ function inline(text) {
|
|
|
18616
19252
|
function layout(opts, body) {
|
|
18617
19253
|
const topItems = [
|
|
18618
19254
|
{ href: "/", label: "Overview" },
|
|
19255
|
+
{ href: "/timeline", label: "Timeline" },
|
|
18619
19256
|
{ href: "/board", label: "Board" },
|
|
18620
19257
|
{ href: "/gar", label: "GAR Report" },
|
|
18621
19258
|
{ href: "/health", label: "Health" }
|
|
@@ -18652,7 +19289,7 @@ function layout(opts, body) {
|
|
|
18652
19289
|
${groupsHtml}
|
|
18653
19290
|
</nav>
|
|
18654
19291
|
</aside>
|
|
18655
|
-
<main class="main">
|
|
19292
|
+
<main class="main${opts.mainClass ? ` ${opts.mainClass}` : ""}">
|
|
18656
19293
|
<button class="expand-toggle" onclick="document.querySelector('.main').classList.toggle('expanded')" title="Toggle wide view">
|
|
18657
19294
|
<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>
|
|
18658
19295
|
<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>
|
|
@@ -18661,7 +19298,36 @@ function layout(opts, body) {
|
|
|
18661
19298
|
</main>
|
|
18662
19299
|
</div>
|
|
18663
19300
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
18664
|
-
<script>mermaid.initialize({
|
|
19301
|
+
<script>mermaid.initialize({
|
|
19302
|
+
startOnLoad: true,
|
|
19303
|
+
theme: 'dark',
|
|
19304
|
+
themeVariables: {
|
|
19305
|
+
background: '#1a1d27',
|
|
19306
|
+
primaryColor: '#2a2e3a',
|
|
19307
|
+
sectionBkgColor: '#1a1d27',
|
|
19308
|
+
sectionBkgColor2: '#222632',
|
|
19309
|
+
altSectionBkgColor: '#222632',
|
|
19310
|
+
gridColor: '#2a2e3a',
|
|
19311
|
+
taskBorderColor: '#475569',
|
|
19312
|
+
doneTaskBkgColor: '#065f46',
|
|
19313
|
+
doneTaskBorderColor: '#34d399',
|
|
19314
|
+
activeTaskBkgColor: '#78350f',
|
|
19315
|
+
activeTaskBorderColor: '#fbbf24',
|
|
19316
|
+
taskTextColor: '#e1e4ea',
|
|
19317
|
+
sectionBkgColor: '#1a1d27',
|
|
19318
|
+
pie1: '#34d399',
|
|
19319
|
+
pie2: '#475569',
|
|
19320
|
+
pie3: '#fbbf24',
|
|
19321
|
+
pie4: '#f87171',
|
|
19322
|
+
pie5: '#6c8cff',
|
|
19323
|
+
pie6: '#a78bfa',
|
|
19324
|
+
pie7: '#f472b6',
|
|
19325
|
+
pieTitleTextColor: '#e1e4ea',
|
|
19326
|
+
pieSectionTextColor: '#e1e4ea',
|
|
19327
|
+
pieLegendTextColor: '#e1e4ea',
|
|
19328
|
+
pieStrokeColor: '#1a1d27'
|
|
19329
|
+
}
|
|
19330
|
+
});</script>
|
|
18665
19331
|
</body>
|
|
18666
19332
|
</html>`;
|
|
18667
19333
|
}
|
|
@@ -18908,6 +19574,10 @@ a:hover { text-decoration: underline; }
|
|
|
18908
19574
|
/* Table */
|
|
18909
19575
|
.table-wrap {
|
|
18910
19576
|
overflow-x: auto;
|
|
19577
|
+
overflow-y: auto;
|
|
19578
|
+
max-height: calc(100vh - 280px);
|
|
19579
|
+
border: 1px solid var(--border);
|
|
19580
|
+
border-radius: var(--radius);
|
|
18911
19581
|
}
|
|
18912
19582
|
|
|
18913
19583
|
table {
|
|
@@ -18923,6 +19593,10 @@ th {
|
|
|
18923
19593
|
letter-spacing: 0.05em;
|
|
18924
19594
|
color: var(--text-dim);
|
|
18925
19595
|
border-bottom: 1px solid var(--border);
|
|
19596
|
+
position: sticky;
|
|
19597
|
+
top: 0;
|
|
19598
|
+
background: var(--bg-card);
|
|
19599
|
+
z-index: 1;
|
|
18926
19600
|
}
|
|
18927
19601
|
|
|
18928
19602
|
td {
|
|
@@ -18970,6 +19644,8 @@ tr:hover td {
|
|
|
18970
19644
|
border: 1px solid var(--border);
|
|
18971
19645
|
border-radius: var(--radius);
|
|
18972
19646
|
padding: 1.25rem;
|
|
19647
|
+
display: flex;
|
|
19648
|
+
flex-direction: column;
|
|
18973
19649
|
}
|
|
18974
19650
|
|
|
18975
19651
|
.gar-area .area-header {
|
|
@@ -19000,6 +19676,9 @@ tr:hover td {
|
|
|
19000
19676
|
.gar-area ul {
|
|
19001
19677
|
list-style: none;
|
|
19002
19678
|
font-size: 0.8rem;
|
|
19679
|
+
max-height: 200px;
|
|
19680
|
+
overflow-y: auto;
|
|
19681
|
+
scrollbar-width: thin;
|
|
19003
19682
|
}
|
|
19004
19683
|
|
|
19005
19684
|
.gar-area li {
|
|
@@ -19022,13 +19701,14 @@ tr:hover td {
|
|
|
19022
19701
|
display: flex;
|
|
19023
19702
|
gap: 1rem;
|
|
19024
19703
|
overflow-x: auto;
|
|
19704
|
+
scrollbar-width: thin;
|
|
19025
19705
|
padding-bottom: 1rem;
|
|
19026
19706
|
}
|
|
19027
19707
|
|
|
19028
19708
|
.board-column {
|
|
19029
19709
|
min-width: 240px;
|
|
19030
19710
|
max-width: 300px;
|
|
19031
|
-
flex:
|
|
19711
|
+
flex: 0 0 auto;
|
|
19032
19712
|
}
|
|
19033
19713
|
|
|
19034
19714
|
.board-column-header {
|
|
@@ -19041,6 +19721,7 @@ tr:hover td {
|
|
|
19041
19721
|
margin-bottom: 0.5rem;
|
|
19042
19722
|
display: flex;
|
|
19043
19723
|
justify-content: space-between;
|
|
19724
|
+
flex-shrink: 0;
|
|
19044
19725
|
}
|
|
19045
19726
|
|
|
19046
19727
|
.board-column-header .count {
|
|
@@ -19222,6 +19903,241 @@ tr:hover td {
|
|
|
19222
19903
|
.mermaid-row .mermaid-container {
|
|
19223
19904
|
margin: 0;
|
|
19224
19905
|
}
|
|
19906
|
+
|
|
19907
|
+
/* Three-column artifact flow */
|
|
19908
|
+
.flow-diagram {
|
|
19909
|
+
background: var(--bg-card);
|
|
19910
|
+
border: 1px solid var(--border);
|
|
19911
|
+
border-radius: var(--radius);
|
|
19912
|
+
padding: 1.25rem;
|
|
19913
|
+
position: relative;
|
|
19914
|
+
overflow-x: auto;
|
|
19915
|
+
}
|
|
19916
|
+
|
|
19917
|
+
.flow-lines {
|
|
19918
|
+
position: absolute;
|
|
19919
|
+
top: 0;
|
|
19920
|
+
left: 0;
|
|
19921
|
+
pointer-events: none;
|
|
19922
|
+
}
|
|
19923
|
+
|
|
19924
|
+
.flow-columns {
|
|
19925
|
+
display: flex;
|
|
19926
|
+
gap: 3rem;
|
|
19927
|
+
position: relative;
|
|
19928
|
+
min-width: 600px;
|
|
19929
|
+
}
|
|
19930
|
+
|
|
19931
|
+
.flow-column {
|
|
19932
|
+
flex: 1;
|
|
19933
|
+
min-width: 0;
|
|
19934
|
+
display: flex;
|
|
19935
|
+
flex-direction: column;
|
|
19936
|
+
gap: 0.5rem;
|
|
19937
|
+
}
|
|
19938
|
+
|
|
19939
|
+
.flow-column-header {
|
|
19940
|
+
font-size: 0.7rem;
|
|
19941
|
+
text-transform: uppercase;
|
|
19942
|
+
letter-spacing: 0.06em;
|
|
19943
|
+
color: var(--text-dim);
|
|
19944
|
+
font-weight: 600;
|
|
19945
|
+
padding-bottom: 0.4rem;
|
|
19946
|
+
border-bottom: 1px solid var(--border);
|
|
19947
|
+
margin-bottom: 0.25rem;
|
|
19948
|
+
}
|
|
19949
|
+
|
|
19950
|
+
.flow-node {
|
|
19951
|
+
padding: 0.5rem 0.65rem;
|
|
19952
|
+
border-radius: 6px;
|
|
19953
|
+
border-left: 3px solid var(--border);
|
|
19954
|
+
background: var(--bg);
|
|
19955
|
+
transition: border-color 0.15s, background 0.15s;
|
|
19956
|
+
}
|
|
19957
|
+
|
|
19958
|
+
.flow-node:hover {
|
|
19959
|
+
background: var(--bg-hover);
|
|
19960
|
+
}
|
|
19961
|
+
|
|
19962
|
+
.flow-node-id {
|
|
19963
|
+
display: inline-block;
|
|
19964
|
+
font-family: var(--mono);
|
|
19965
|
+
font-size: 0.65rem;
|
|
19966
|
+
color: var(--accent);
|
|
19967
|
+
margin-bottom: 0.15rem;
|
|
19968
|
+
text-decoration: none;
|
|
19969
|
+
}
|
|
19970
|
+
|
|
19971
|
+
.flow-node-id:hover {
|
|
19972
|
+
text-decoration: underline;
|
|
19973
|
+
}
|
|
19974
|
+
|
|
19975
|
+
.flow-node-title {
|
|
19976
|
+
display: block;
|
|
19977
|
+
font-size: 0.8rem;
|
|
19978
|
+
}
|
|
19979
|
+
|
|
19980
|
+
.flow-done { border-left-color: var(--green); }
|
|
19981
|
+
.flow-active { border-left-color: var(--amber); }
|
|
19982
|
+
.flow-blocked { border-left-color: var(--red); }
|
|
19983
|
+
.flow-default { border-left-color: var(--accent-dim); }
|
|
19984
|
+
|
|
19985
|
+
.flow-node { cursor: pointer; transition: opacity 0.2s, border-color 0.15s, background 0.15s; }
|
|
19986
|
+
.flow-dim { opacity: 0.2; }
|
|
19987
|
+
.flow-lit { background: var(--bg-hover); }
|
|
19988
|
+
.flow-line-lit { stroke: var(--accent) !important; stroke-width: 2 !important; }
|
|
19989
|
+
.flow-line-dim { opacity: 0.08; }
|
|
19990
|
+
|
|
19991
|
+
/* Gantt truncation note */
|
|
19992
|
+
.mermaid-note {
|
|
19993
|
+
font-size: 0.75rem;
|
|
19994
|
+
color: var(--text-dim);
|
|
19995
|
+
text-align: right;
|
|
19996
|
+
margin-bottom: 0.5rem;
|
|
19997
|
+
}
|
|
19998
|
+
|
|
19999
|
+
/* HTML Gantt chart */
|
|
20000
|
+
.gantt {
|
|
20001
|
+
background: var(--bg-card);
|
|
20002
|
+
border: 1px solid var(--border);
|
|
20003
|
+
border-radius: var(--radius);
|
|
20004
|
+
padding: 1.25rem 1.25rem 1.25rem 0;
|
|
20005
|
+
position: relative;
|
|
20006
|
+
overflow-x: auto;
|
|
20007
|
+
}
|
|
20008
|
+
|
|
20009
|
+
.gantt-chart {
|
|
20010
|
+
min-width: 600px;
|
|
20011
|
+
}
|
|
20012
|
+
|
|
20013
|
+
.gantt-overlay {
|
|
20014
|
+
position: absolute;
|
|
20015
|
+
top: 0;
|
|
20016
|
+
left: 0;
|
|
20017
|
+
right: 0;
|
|
20018
|
+
bottom: 0;
|
|
20019
|
+
pointer-events: none;
|
|
20020
|
+
display: flex;
|
|
20021
|
+
}
|
|
20022
|
+
|
|
20023
|
+
.gantt-header,
|
|
20024
|
+
.gantt-section-row,
|
|
20025
|
+
.gantt-row,
|
|
20026
|
+
.gantt-overlay {
|
|
20027
|
+
display: flex;
|
|
20028
|
+
align-items: center;
|
|
20029
|
+
}
|
|
20030
|
+
|
|
20031
|
+
.gantt-label {
|
|
20032
|
+
width: 200px;
|
|
20033
|
+
min-width: 200px;
|
|
20034
|
+
padding: 0.3rem 0.75rem;
|
|
20035
|
+
font-size: 0.8rem;
|
|
20036
|
+
color: var(--text-dim);
|
|
20037
|
+
text-align: right;
|
|
20038
|
+
white-space: nowrap;
|
|
20039
|
+
overflow: hidden;
|
|
20040
|
+
text-overflow: ellipsis;
|
|
20041
|
+
}
|
|
20042
|
+
|
|
20043
|
+
.gantt-section-label {
|
|
20044
|
+
font-weight: 600;
|
|
20045
|
+
color: var(--text);
|
|
20046
|
+
font-size: 0.75rem;
|
|
20047
|
+
text-transform: uppercase;
|
|
20048
|
+
letter-spacing: 0.03em;
|
|
20049
|
+
padding-top: 0.6rem;
|
|
20050
|
+
}
|
|
20051
|
+
|
|
20052
|
+
.gantt-track {
|
|
20053
|
+
flex: 1;
|
|
20054
|
+
position: relative;
|
|
20055
|
+
height: 28px;
|
|
20056
|
+
min-width: 0;
|
|
20057
|
+
}
|
|
20058
|
+
|
|
20059
|
+
.gantt-section-row .gantt-track {
|
|
20060
|
+
height: 20px;
|
|
20061
|
+
}
|
|
20062
|
+
|
|
20063
|
+
.gantt-section-bg {
|
|
20064
|
+
position: absolute;
|
|
20065
|
+
top: 0;
|
|
20066
|
+
bottom: 0;
|
|
20067
|
+
background: var(--bg-hover);
|
|
20068
|
+
border-radius: 3px;
|
|
20069
|
+
opacity: 0.4;
|
|
20070
|
+
}
|
|
20071
|
+
|
|
20072
|
+
.gantt-bar {
|
|
20073
|
+
position: absolute;
|
|
20074
|
+
top: 4px;
|
|
20075
|
+
bottom: 4px;
|
|
20076
|
+
border-radius: 4px;
|
|
20077
|
+
min-width: 6px;
|
|
20078
|
+
transition: opacity 0.15s;
|
|
20079
|
+
}
|
|
20080
|
+
|
|
20081
|
+
.gantt-bar:hover {
|
|
20082
|
+
opacity: 0.85;
|
|
20083
|
+
}
|
|
20084
|
+
|
|
20085
|
+
.gantt-bar-done {
|
|
20086
|
+
background: var(--green);
|
|
20087
|
+
}
|
|
20088
|
+
|
|
20089
|
+
.gantt-bar-active {
|
|
20090
|
+
background: var(--amber);
|
|
20091
|
+
}
|
|
20092
|
+
|
|
20093
|
+
.gantt-bar-blocked {
|
|
20094
|
+
background: var(--red);
|
|
20095
|
+
}
|
|
20096
|
+
|
|
20097
|
+
.gantt-bar-default {
|
|
20098
|
+
background: var(--accent-dim);
|
|
20099
|
+
}
|
|
20100
|
+
|
|
20101
|
+
.gantt-dates {
|
|
20102
|
+
height: 24px;
|
|
20103
|
+
border-bottom: 1px solid var(--border);
|
|
20104
|
+
margin-bottom: 0.25rem;
|
|
20105
|
+
}
|
|
20106
|
+
|
|
20107
|
+
.gantt-marker {
|
|
20108
|
+
position: absolute;
|
|
20109
|
+
top: 0;
|
|
20110
|
+
bottom: 0;
|
|
20111
|
+
border-left: 1px solid var(--border);
|
|
20112
|
+
}
|
|
20113
|
+
|
|
20114
|
+
.gantt-marker span {
|
|
20115
|
+
position: absolute;
|
|
20116
|
+
top: 2px;
|
|
20117
|
+
left: 6px;
|
|
20118
|
+
font-size: 0.65rem;
|
|
20119
|
+
color: var(--text-dim);
|
|
20120
|
+
white-space: nowrap;
|
|
20121
|
+
}
|
|
20122
|
+
|
|
20123
|
+
.gantt-today {
|
|
20124
|
+
position: absolute;
|
|
20125
|
+
top: 0;
|
|
20126
|
+
bottom: 0;
|
|
20127
|
+
width: 2px;
|
|
20128
|
+
background: var(--red);
|
|
20129
|
+
opacity: 0.7;
|
|
20130
|
+
}
|
|
20131
|
+
|
|
20132
|
+
/* Pie chart color overrides */
|
|
20133
|
+
.mermaid-container .pieCircle {
|
|
20134
|
+
stroke: var(--bg-card);
|
|
20135
|
+
}
|
|
20136
|
+
|
|
20137
|
+
.mermaid-container text.slice {
|
|
20138
|
+
fill: var(--bg) !important;
|
|
20139
|
+
font-weight: 600;
|
|
20140
|
+
}
|
|
19225
20141
|
`;
|
|
19226
20142
|
}
|
|
19227
20143
|
|
|
@@ -19230,98 +20146,275 @@ function sanitize(text, maxLen = 40) {
|
|
|
19230
20146
|
const cleaned = text.replace(/["'`]/g, "").replace(/[\r\n]+/g, " ");
|
|
19231
20147
|
return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 1) + "\u2026" : cleaned;
|
|
19232
20148
|
}
|
|
19233
|
-
function mermaidBlock(definition) {
|
|
19234
|
-
|
|
20149
|
+
function mermaidBlock(definition, extraClass) {
|
|
20150
|
+
const cls = ["mermaid-container", extraClass].filter(Boolean).join(" ");
|
|
20151
|
+
return `<div class="${cls}"><pre class="mermaid">
|
|
19235
20152
|
${definition}
|
|
19236
20153
|
</pre></div>`;
|
|
19237
20154
|
}
|
|
19238
20155
|
function placeholder(message) {
|
|
19239
20156
|
return `<div class="mermaid-container mermaid-empty"><p>${message}</p></div>`;
|
|
19240
20157
|
}
|
|
19241
|
-
function
|
|
19242
|
-
|
|
20158
|
+
function toMs(date5) {
|
|
20159
|
+
return (/* @__PURE__ */ new Date(date5 + "T00:00:00")).getTime();
|
|
20160
|
+
}
|
|
20161
|
+
function fmtDate(ms) {
|
|
20162
|
+
const d = new Date(ms);
|
|
20163
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
20164
|
+
return `${months[d.getMonth()]} ${d.getDate()}`;
|
|
20165
|
+
}
|
|
20166
|
+
function buildTimelineGantt(data, maxSprints = 6) {
|
|
20167
|
+
const sprintsWithDates = data.sprints.filter((s) => s.startDate && s.endDate).sort((a, b) => a.startDate < b.startDate ? -1 : 1);
|
|
19243
20168
|
if (sprintsWithDates.length === 0) {
|
|
19244
20169
|
return placeholder("No timeline data available \u2014 sprints need start and end dates.");
|
|
19245
20170
|
}
|
|
20171
|
+
const truncated = sprintsWithDates.length > maxSprints;
|
|
20172
|
+
const visibleSprints = truncated ? sprintsWithDates.slice(-maxSprints) : sprintsWithDates;
|
|
20173
|
+
const hiddenCount = sprintsWithDates.length - visibleSprints.length;
|
|
19246
20174
|
const epicMap = new Map(data.epics.map((e) => [e.id, e]));
|
|
19247
|
-
const
|
|
19248
|
-
|
|
19249
|
-
|
|
20175
|
+
const allStarts = visibleSprints.map((s) => toMs(s.startDate));
|
|
20176
|
+
const allEnds = visibleSprints.map((s) => toMs(s.endDate));
|
|
20177
|
+
const timelineStart = Math.min(...allStarts);
|
|
20178
|
+
const timelineEnd = Math.max(...allEnds);
|
|
20179
|
+
const span = timelineEnd - timelineStart || 1;
|
|
20180
|
+
const pct = (ms) => (ms - timelineStart) / span * 100;
|
|
20181
|
+
const DAY = 864e5;
|
|
20182
|
+
const markers = [];
|
|
20183
|
+
let tick = timelineStart;
|
|
20184
|
+
const startDay = new Date(tick).getDay();
|
|
20185
|
+
tick += (8 - startDay) % 7 * DAY;
|
|
20186
|
+
while (tick <= timelineEnd) {
|
|
20187
|
+
const left = pct(tick);
|
|
20188
|
+
markers.push(
|
|
20189
|
+
`<div class="gantt-marker" style="left:${left.toFixed(2)}%"><span>${fmtDate(tick)}</span></div>`
|
|
20190
|
+
);
|
|
20191
|
+
tick += 7 * DAY;
|
|
20192
|
+
}
|
|
20193
|
+
const now = Date.now();
|
|
20194
|
+
let todayMarker = "";
|
|
20195
|
+
if (now >= timelineStart && now <= timelineEnd) {
|
|
20196
|
+
todayMarker = `<div class="gantt-today" style="left:${pct(now).toFixed(2)}%"></div>`;
|
|
20197
|
+
}
|
|
20198
|
+
const rows = [];
|
|
20199
|
+
for (const sprint of visibleSprints) {
|
|
20200
|
+
const sStart = toMs(sprint.startDate);
|
|
20201
|
+
const sEnd = toMs(sprint.endDate);
|
|
20202
|
+
rows.push(`<div class="gantt-section-row">
|
|
20203
|
+
<div class="gantt-label gantt-section-label">${sanitize(sprint.id + " " + sprint.title, 50)}</div>
|
|
20204
|
+
<div class="gantt-track">
|
|
20205
|
+
<div class="gantt-section-bg" style="left:${pct(sStart).toFixed(2)}%;width:${(pct(sEnd) - pct(sStart)).toFixed(2)}%"></div>
|
|
20206
|
+
</div>
|
|
20207
|
+
</div>`);
|
|
19250
20208
|
const linked = sprint.linkedEpics.map((eid) => epicMap.get(eid)).filter(Boolean);
|
|
19251
|
-
|
|
19252
|
-
|
|
19253
|
-
|
|
19254
|
-
|
|
19255
|
-
|
|
19256
|
-
|
|
19257
|
-
|
|
20209
|
+
const items = linked.length > 0 ? linked.map((e) => ({ label: sanitize(e.id + " " + e.title), status: e.status })) : [{ label: sanitize(sprint.title), status: sprint.status }];
|
|
20210
|
+
for (const item of items) {
|
|
20211
|
+
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";
|
|
20212
|
+
const left = pct(sStart).toFixed(2);
|
|
20213
|
+
const width = (pct(sEnd) - pct(sStart)).toFixed(2);
|
|
20214
|
+
rows.push(`<div class="gantt-row">
|
|
20215
|
+
<div class="gantt-label">${item.label}</div>
|
|
20216
|
+
<div class="gantt-track">
|
|
20217
|
+
<div class="gantt-bar ${cls}" style="left:${left}%;width:${width}%"></div>
|
|
20218
|
+
</div>
|
|
20219
|
+
</div>`);
|
|
19258
20220
|
}
|
|
19259
20221
|
}
|
|
19260
|
-
|
|
20222
|
+
const note = truncated ? `<div class="mermaid-note">${hiddenCount} earlier sprint${hiddenCount > 1 ? "s" : ""} not shown</div>` : "";
|
|
20223
|
+
return `${note}
|
|
20224
|
+
<div class="gantt">
|
|
20225
|
+
<div class="gantt-chart">
|
|
20226
|
+
<div class="gantt-header">
|
|
20227
|
+
<div class="gantt-label"></div>
|
|
20228
|
+
<div class="gantt-track gantt-dates">${markers.join("")}</div>
|
|
20229
|
+
</div>
|
|
20230
|
+
${rows.join("\n")}
|
|
20231
|
+
</div>
|
|
20232
|
+
<div class="gantt-overlay">
|
|
20233
|
+
<div class="gantt-label"></div>
|
|
20234
|
+
<div class="gantt-track">${todayMarker}</div>
|
|
20235
|
+
</div>
|
|
20236
|
+
</div>`;
|
|
20237
|
+
}
|
|
20238
|
+
function statusClass(status) {
|
|
20239
|
+
const s = status.toLowerCase();
|
|
20240
|
+
if (s === "done" || s === "completed") return "flow-done";
|
|
20241
|
+
if (s === "in-progress" || s === "active") return "flow-active";
|
|
20242
|
+
if (s === "blocked") return "flow-blocked";
|
|
20243
|
+
return "flow-default";
|
|
19261
20244
|
}
|
|
19262
20245
|
function buildArtifactFlowchart(data) {
|
|
19263
20246
|
if (data.features.length === 0 && data.epics.length === 0) {
|
|
19264
20247
|
return placeholder("No artifact relationships found \u2014 create features and epics to see the hierarchy.");
|
|
19265
20248
|
}
|
|
19266
|
-
const
|
|
19267
|
-
|
|
19268
|
-
lines.push(" classDef inprogress fill:#78350f,stroke:#fbbf24,color:#fef3c7");
|
|
19269
|
-
lines.push(" classDef blocked fill:#7f1d1d,stroke:#f87171,color:#fee2e2");
|
|
19270
|
-
lines.push(" classDef default fill:#1e293b,stroke:#475569,color:#e2e8f0");
|
|
19271
|
-
const nodeIds = /* @__PURE__ */ new Set();
|
|
20249
|
+
const edges = [];
|
|
20250
|
+
const epicsByFeature = /* @__PURE__ */ new Map();
|
|
19272
20251
|
for (const epic of data.epics) {
|
|
19273
|
-
for (const
|
|
19274
|
-
|
|
19275
|
-
|
|
19276
|
-
|
|
19277
|
-
const eNode = epic.id.replace(/-/g, "_");
|
|
19278
|
-
if (!nodeIds.has(fNode)) {
|
|
19279
|
-
lines.push(` ${fNode}["${sanitize(feature.id + " " + feature.title)}"]`);
|
|
19280
|
-
nodeIds.add(fNode);
|
|
19281
|
-
}
|
|
19282
|
-
if (!nodeIds.has(eNode)) {
|
|
19283
|
-
lines.push(` ${eNode}["${sanitize(epic.id + " " + epic.title)}"]`);
|
|
19284
|
-
nodeIds.add(eNode);
|
|
19285
|
-
}
|
|
19286
|
-
lines.push(` ${fNode} --> ${eNode}`);
|
|
19287
|
-
}
|
|
20252
|
+
for (const fid of epic.linkedFeature) {
|
|
20253
|
+
if (!epicsByFeature.has(fid)) epicsByFeature.set(fid, []);
|
|
20254
|
+
epicsByFeature.get(fid).push(epic.id);
|
|
20255
|
+
edges.push({ from: fid, to: epic.id });
|
|
19288
20256
|
}
|
|
19289
20257
|
}
|
|
20258
|
+
const sprintsByEpic = /* @__PURE__ */ new Map();
|
|
19290
20259
|
for (const sprint of data.sprints) {
|
|
19291
|
-
const
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
|
|
19295
|
-
const eNode = epic.id.replace(/-/g, "_");
|
|
19296
|
-
if (!nodeIds.has(eNode)) {
|
|
19297
|
-
lines.push(` ${eNode}["${sanitize(epic.id + " " + epic.title)}"]`);
|
|
19298
|
-
nodeIds.add(eNode);
|
|
19299
|
-
}
|
|
19300
|
-
if (!nodeIds.has(sNode)) {
|
|
19301
|
-
lines.push(` ${sNode}["${sanitize(sprint.id + " " + sprint.title)}"]`);
|
|
19302
|
-
nodeIds.add(sNode);
|
|
19303
|
-
}
|
|
19304
|
-
lines.push(` ${eNode} --> ${sNode}`);
|
|
19305
|
-
}
|
|
20260
|
+
for (const eid of sprint.linkedEpics) {
|
|
20261
|
+
if (!sprintsByEpic.has(eid)) sprintsByEpic.set(eid, []);
|
|
20262
|
+
sprintsByEpic.get(eid).push(sprint.id);
|
|
20263
|
+
edges.push({ from: eid, to: sprint.id });
|
|
19306
20264
|
}
|
|
19307
20265
|
}
|
|
19308
|
-
|
|
20266
|
+
const connectedFeatureIds = new Set(epicsByFeature.keys());
|
|
20267
|
+
const connectedEpicIds = /* @__PURE__ */ new Set();
|
|
20268
|
+
for (const ids of epicsByFeature.values()) ids.forEach((id) => connectedEpicIds.add(id));
|
|
20269
|
+
for (const ids of sprintsByEpic.values()) ids.forEach(() => {
|
|
20270
|
+
});
|
|
20271
|
+
for (const eid of sprintsByEpic.keys()) connectedEpicIds.add(eid);
|
|
20272
|
+
const connectedSprintIds = /* @__PURE__ */ new Set();
|
|
20273
|
+
for (const ids of sprintsByEpic.values()) ids.forEach((id) => connectedSprintIds.add(id));
|
|
20274
|
+
const features = data.features.filter((f) => connectedFeatureIds.has(f.id));
|
|
20275
|
+
const epics = data.epics.filter((e) => connectedEpicIds.has(e.id));
|
|
20276
|
+
const sprints = data.sprints.filter((s) => connectedSprintIds.has(s.id)).sort((a, b) => (a.startDate ?? "").localeCompare(b.startDate ?? ""));
|
|
20277
|
+
if (features.length === 0 && epics.length === 0) {
|
|
19309
20278
|
return placeholder("No artifact relationships found \u2014 link epics to features and sprints.");
|
|
19310
20279
|
}
|
|
19311
|
-
const
|
|
19312
|
-
|
|
19313
|
-
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
lines
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
20280
|
+
const renderNode = (id, title, status, type) => `<div class="flow-node ${statusClass(status)}" data-flow-id="${id}">
|
|
20281
|
+
<a class="flow-node-id" href="/docs/${type}/${id}">${id}</a>
|
|
20282
|
+
<span class="flow-node-title">${sanitize(title, 35)}</span>
|
|
20283
|
+
</div>`;
|
|
20284
|
+
const featuresHtml = features.map((f) => renderNode(f.id, f.title, f.status, "feature")).join("\n");
|
|
20285
|
+
const epicsHtml = epics.map((e) => renderNode(e.id, e.title, e.status, "epic")).join("\n");
|
|
20286
|
+
const sprintsHtml = sprints.map((s) => renderNode(s.id, s.title, s.status, "sprint")).join("\n");
|
|
20287
|
+
const edgesJson = JSON.stringify(edges);
|
|
20288
|
+
return `
|
|
20289
|
+
<div class="flow-diagram" id="flow-diagram">
|
|
20290
|
+
<svg class="flow-lines" id="flow-lines"></svg>
|
|
20291
|
+
<div class="flow-columns">
|
|
20292
|
+
<div class="flow-column">
|
|
20293
|
+
<div class="flow-column-header">Features</div>
|
|
20294
|
+
${featuresHtml}
|
|
20295
|
+
</div>
|
|
20296
|
+
<div class="flow-column">
|
|
20297
|
+
<div class="flow-column-header">Epics</div>
|
|
20298
|
+
${epicsHtml}
|
|
20299
|
+
</div>
|
|
20300
|
+
<div class="flow-column">
|
|
20301
|
+
<div class="flow-column-header">Sprints</div>
|
|
20302
|
+
${sprintsHtml}
|
|
20303
|
+
</div>
|
|
20304
|
+
</div>
|
|
20305
|
+
</div>
|
|
20306
|
+
<script>
|
|
20307
|
+
(function() {
|
|
20308
|
+
var edges = ${edgesJson};
|
|
20309
|
+
var container = document.getElementById('flow-diagram');
|
|
20310
|
+
var svg = document.getElementById('flow-lines');
|
|
20311
|
+
if (!container || !svg) return;
|
|
20312
|
+
|
|
20313
|
+
// Build adjacency map (bidirectional) for traversal
|
|
20314
|
+
var adj = {};
|
|
20315
|
+
edges.forEach(function(e) {
|
|
20316
|
+
if (!adj[e.from]) adj[e.from] = [];
|
|
20317
|
+
if (!adj[e.to]) adj[e.to] = [];
|
|
20318
|
+
adj[e.from].push(e.to);
|
|
20319
|
+
adj[e.to].push(e.from);
|
|
20320
|
+
});
|
|
20321
|
+
|
|
20322
|
+
function drawLines() {
|
|
20323
|
+
var rect = container.getBoundingClientRect();
|
|
20324
|
+
svg.setAttribute('width', rect.width);
|
|
20325
|
+
svg.setAttribute('height', rect.height);
|
|
20326
|
+
svg.innerHTML = '';
|
|
20327
|
+
|
|
20328
|
+
edges.forEach(function(edge) {
|
|
20329
|
+
var fromEl = container.querySelector('[data-flow-id="' + edge.from + '"]');
|
|
20330
|
+
var toEl = container.querySelector('[data-flow-id="' + edge.to + '"]');
|
|
20331
|
+
if (!fromEl || !toEl) return;
|
|
20332
|
+
|
|
20333
|
+
var fr = fromEl.getBoundingClientRect();
|
|
20334
|
+
var tr = toEl.getBoundingClientRect();
|
|
20335
|
+
var x1 = fr.right - rect.left;
|
|
20336
|
+
var y1 = fr.top + fr.height / 2 - rect.top;
|
|
20337
|
+
var x2 = tr.left - rect.left;
|
|
20338
|
+
var y2 = tr.top + tr.height / 2 - rect.top;
|
|
20339
|
+
var mx = (x1 + x2) / 2;
|
|
20340
|
+
|
|
20341
|
+
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
20342
|
+
path.setAttribute('d', 'M' + x1 + ',' + y1 + ' C' + mx + ',' + y1 + ' ' + mx + ',' + y2 + ' ' + x2 + ',' + y2);
|
|
20343
|
+
path.setAttribute('fill', 'none');
|
|
20344
|
+
path.setAttribute('stroke', '#2a2e3a');
|
|
20345
|
+
path.setAttribute('stroke-width', '1.5');
|
|
20346
|
+
path.dataset.from = edge.from;
|
|
20347
|
+
path.dataset.to = edge.to;
|
|
20348
|
+
svg.appendChild(path);
|
|
20349
|
+
});
|
|
20350
|
+
}
|
|
20351
|
+
|
|
20352
|
+
// Find all nodes reachable from a starting node
|
|
20353
|
+
function findConnected(startId) {
|
|
20354
|
+
var visited = {};
|
|
20355
|
+
var queue = [startId];
|
|
20356
|
+
visited[startId] = true;
|
|
20357
|
+
while (queue.length) {
|
|
20358
|
+
var id = queue.shift();
|
|
20359
|
+
(adj[id] || []).forEach(function(neighbor) {
|
|
20360
|
+
if (!visited[neighbor]) {
|
|
20361
|
+
visited[neighbor] = true;
|
|
20362
|
+
queue.push(neighbor);
|
|
20363
|
+
}
|
|
20364
|
+
});
|
|
20365
|
+
}
|
|
20366
|
+
return visited;
|
|
20367
|
+
}
|
|
20368
|
+
|
|
20369
|
+
function highlight(hoveredId) {
|
|
20370
|
+
var connected = findConnected(hoveredId);
|
|
20371
|
+
container.querySelectorAll('.flow-node').forEach(function(n) {
|
|
20372
|
+
if (connected[n.dataset.flowId]) {
|
|
20373
|
+
n.classList.add('flow-lit');
|
|
20374
|
+
n.classList.remove('flow-dim');
|
|
20375
|
+
} else {
|
|
20376
|
+
n.classList.add('flow-dim');
|
|
20377
|
+
n.classList.remove('flow-lit');
|
|
20378
|
+
}
|
|
20379
|
+
});
|
|
20380
|
+
svg.querySelectorAll('path').forEach(function(p) {
|
|
20381
|
+
if (connected[p.dataset.from] && connected[p.dataset.to]) {
|
|
20382
|
+
p.classList.add('flow-line-lit');
|
|
20383
|
+
p.classList.remove('flow-line-dim');
|
|
20384
|
+
} else {
|
|
20385
|
+
p.classList.add('flow-line-dim');
|
|
20386
|
+
p.classList.remove('flow-line-lit');
|
|
20387
|
+
}
|
|
20388
|
+
});
|
|
20389
|
+
}
|
|
20390
|
+
|
|
20391
|
+
function clearHighlight() {
|
|
20392
|
+
container.querySelectorAll('.flow-node').forEach(function(n) { n.classList.remove('flow-lit', 'flow-dim'); });
|
|
20393
|
+
svg.querySelectorAll('path').forEach(function(p) { p.classList.remove('flow-line-lit', 'flow-line-dim'); });
|
|
20394
|
+
}
|
|
20395
|
+
|
|
20396
|
+
var activeId = null;
|
|
20397
|
+
container.addEventListener('click', function(e) {
|
|
20398
|
+
// Let the ID link navigate normally
|
|
20399
|
+
if (e.target.closest('a')) return;
|
|
20400
|
+
|
|
20401
|
+
var node = e.target.closest('.flow-node');
|
|
20402
|
+
var clickedId = node ? node.dataset.flowId : null;
|
|
20403
|
+
|
|
20404
|
+
if (!clickedId || clickedId === activeId) {
|
|
20405
|
+
activeId = null;
|
|
20406
|
+
clearHighlight();
|
|
20407
|
+
return;
|
|
20408
|
+
}
|
|
20409
|
+
|
|
20410
|
+
activeId = clickedId;
|
|
20411
|
+
highlight(clickedId);
|
|
20412
|
+
});
|
|
20413
|
+
|
|
20414
|
+
requestAnimationFrame(function() { setTimeout(drawLines, 100); });
|
|
20415
|
+
window.addEventListener('resize', drawLines);
|
|
20416
|
+
})();
|
|
20417
|
+
</script>`;
|
|
19325
20418
|
}
|
|
19326
20419
|
function buildStatusPie(title, counts) {
|
|
19327
20420
|
const entries = Object.entries(counts).filter(([, v]) => v > 0);
|
|
@@ -19401,8 +20494,7 @@ function overviewPage(data, diagrams, navGroups) {
|
|
|
19401
20494
|
${groupSections}
|
|
19402
20495
|
${ungroupedSection}
|
|
19403
20496
|
|
|
19404
|
-
<div class="section-title">Project Timeline
|
|
19405
|
-
${buildTimelineGantt(diagrams)}
|
|
20497
|
+
<div class="section-title"><a href="/timeline">Project Timeline →</a></div>
|
|
19406
20498
|
|
|
19407
20499
|
<div class="section-title">Artifact Relationships</div>
|
|
19408
20500
|
${buildArtifactFlowchart(diagrams)}
|
|
@@ -19657,6 +20749,7 @@ function boardPage(data) {
|
|
|
19657
20749
|
<span>${escapeHtml(col.status)}</span>
|
|
19658
20750
|
<span class="count">${col.docs.length}</span>
|
|
19659
20751
|
</div>
|
|
20752
|
+
<div class="board-column-cards">
|
|
19660
20753
|
${col.docs.map(
|
|
19661
20754
|
(doc) => `
|
|
19662
20755
|
<div class="board-card">
|
|
@@ -19667,6 +20760,7 @@ function boardPage(data) {
|
|
|
19667
20760
|
</a>
|
|
19668
20761
|
</div>`
|
|
19669
20762
|
).join("\n")}
|
|
20763
|
+
</div>
|
|
19670
20764
|
</div>`
|
|
19671
20765
|
).join("\n");
|
|
19672
20766
|
return `
|
|
@@ -19692,6 +20786,18 @@ function boardPage(data) {
|
|
|
19692
20786
|
`;
|
|
19693
20787
|
}
|
|
19694
20788
|
|
|
20789
|
+
// src/web/templates/pages/timeline.ts
|
|
20790
|
+
function timelinePage(diagrams) {
|
|
20791
|
+
return `
|
|
20792
|
+
<div class="page-header">
|
|
20793
|
+
<h2>Project Timeline</h2>
|
|
20794
|
+
<div class="subtitle">Sprint schedule with linked epics</div>
|
|
20795
|
+
</div>
|
|
20796
|
+
|
|
20797
|
+
${buildTimelineGantt(diagrams)}
|
|
20798
|
+
`;
|
|
20799
|
+
}
|
|
20800
|
+
|
|
19695
20801
|
// src/web/router.ts
|
|
19696
20802
|
function handleRequest(req, res, store, projectName, navGroups) {
|
|
19697
20803
|
const parsed = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
@@ -19713,6 +20819,12 @@ function handleRequest(req, res, store, projectName, navGroups) {
|
|
|
19713
20819
|
respond(res, layout({ title: "Overview", activePath: "/", projectName, navGroups }, body));
|
|
19714
20820
|
return;
|
|
19715
20821
|
}
|
|
20822
|
+
if (pathname === "/timeline") {
|
|
20823
|
+
const diagrams = getDiagramData(store);
|
|
20824
|
+
const body = timelinePage(diagrams);
|
|
20825
|
+
respond(res, layout({ title: "Timeline", activePath: "/timeline", projectName, navGroups, mainClass: "expanded" }, body));
|
|
20826
|
+
return;
|
|
20827
|
+
}
|
|
19716
20828
|
if (pathname === "/gar") {
|
|
19717
20829
|
const report = getGarData(store, projectName);
|
|
19718
20830
|
const body = garPage(report);
|
|
@@ -19817,7 +20929,7 @@ function openBrowser(url2) {
|
|
|
19817
20929
|
var runningServer = null;
|
|
19818
20930
|
function createWebTools(store, projectName, navGroups) {
|
|
19819
20931
|
return [
|
|
19820
|
-
|
|
20932
|
+
tool22(
|
|
19821
20933
|
"start_web_dashboard",
|
|
19822
20934
|
"Start the Marvin web dashboard on a local port. Returns the base URL. If already running, returns the existing URL.",
|
|
19823
20935
|
{
|
|
@@ -19849,7 +20961,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19849
20961
|
};
|
|
19850
20962
|
}
|
|
19851
20963
|
),
|
|
19852
|
-
|
|
20964
|
+
tool22(
|
|
19853
20965
|
"stop_web_dashboard",
|
|
19854
20966
|
"Stop the running Marvin web dashboard.",
|
|
19855
20967
|
{},
|
|
@@ -19869,7 +20981,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19869
20981
|
};
|
|
19870
20982
|
}
|
|
19871
20983
|
),
|
|
19872
|
-
|
|
20984
|
+
tool22(
|
|
19873
20985
|
"get_web_dashboard_urls",
|
|
19874
20986
|
"Get all available dashboard page URLs. The dashboard must be running.",
|
|
19875
20987
|
{},
|
|
@@ -19895,7 +21007,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19895
21007
|
},
|
|
19896
21008
|
{ annotations: { readOnlyHint: true } }
|
|
19897
21009
|
),
|
|
19898
|
-
|
|
21010
|
+
tool22(
|
|
19899
21011
|
"get_dashboard_overview",
|
|
19900
21012
|
"Get the project overview data: document type counts and recent activity. Works without the web server running.",
|
|
19901
21013
|
{},
|
|
@@ -19917,7 +21029,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19917
21029
|
},
|
|
19918
21030
|
{ annotations: { readOnlyHint: true } }
|
|
19919
21031
|
),
|
|
19920
|
-
|
|
21032
|
+
tool22(
|
|
19921
21033
|
"get_dashboard_gar",
|
|
19922
21034
|
"Get the GAR (Governance, Actions, Risks) report as JSON. Works without the web server running.",
|
|
19923
21035
|
{},
|
|
@@ -19929,7 +21041,7 @@ function createWebTools(store, projectName, navGroups) {
|
|
|
19929
21041
|
},
|
|
19930
21042
|
{ annotations: { readOnlyHint: true } }
|
|
19931
21043
|
),
|
|
19932
|
-
|
|
21044
|
+
tool22(
|
|
19933
21045
|
"get_dashboard_board",
|
|
19934
21046
|
"Get board data showing documents grouped by status. Optionally filter by document type. Works without the web server running.",
|
|
19935
21047
|
{
|
|
@@ -20043,7 +21155,7 @@ function createSkillActionTools(skills, context) {
|
|
|
20043
21155
|
if (!skill.actions) continue;
|
|
20044
21156
|
for (const action of skill.actions) {
|
|
20045
21157
|
tools.push(
|
|
20046
|
-
|
|
21158
|
+
tool23(
|
|
20047
21159
|
`${skill.id}__${action.id}`,
|
|
20048
21160
|
action.description,
|
|
20049
21161
|
{
|
|
@@ -20134,7 +21246,7 @@ var deliveryManager = {
|
|
|
20134
21246
|
"Epic scheduling and tracking",
|
|
20135
21247
|
"Sprint planning and tracking"
|
|
20136
21248
|
],
|
|
20137
|
-
documentTypes: ["action", "decision", "meeting", "question", "feature", "epic", "sprint"],
|
|
21249
|
+
documentTypes: ["action", "decision", "meeting", "question", "feature", "epic", "task", "sprint"],
|
|
20138
21250
|
contributionTypes: ["risk-finding", "blocker-report", "dependency-update", "status-assessment"]
|
|
20139
21251
|
};
|
|
20140
21252
|
|
|
@@ -20172,9 +21284,10 @@ var techLead = {
|
|
|
20172
21284
|
"Implementation guidance",
|
|
20173
21285
|
"Non-functional requirements",
|
|
20174
21286
|
"Epic creation and scoping",
|
|
21287
|
+
"Task creation and breakdown",
|
|
20175
21288
|
"Sprint scoping and technical execution"
|
|
20176
21289
|
],
|
|
20177
|
-
documentTypes: ["decision", "action", "question", "epic", "sprint"],
|
|
21290
|
+
documentTypes: ["decision", "action", "question", "epic", "task", "sprint"],
|
|
20178
21291
|
contributionTypes: ["action-result", "spike-findings", "technical-assessment", "architecture-review"]
|
|
20179
21292
|
};
|
|
20180
21293
|
|
|
@@ -20272,10 +21385,10 @@ ${lines.join("\n\n")}`;
|
|
|
20272
21385
|
}
|
|
20273
21386
|
|
|
20274
21387
|
// src/mcp/persona-tools.ts
|
|
20275
|
-
import { tool as
|
|
21388
|
+
import { tool as tool24 } from "@anthropic-ai/claude-agent-sdk";
|
|
20276
21389
|
function createPersonaTools(ctx, marvinDir) {
|
|
20277
21390
|
return [
|
|
20278
|
-
|
|
21391
|
+
tool24(
|
|
20279
21392
|
"set_persona",
|
|
20280
21393
|
"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.",
|
|
20281
21394
|
{
|
|
@@ -20305,7 +21418,7 @@ ${summaries}`
|
|
|
20305
21418
|
};
|
|
20306
21419
|
}
|
|
20307
21420
|
),
|
|
20308
|
-
|
|
21421
|
+
tool24(
|
|
20309
21422
|
"get_persona_guidance",
|
|
20310
21423
|
"Get guidance for a persona without changing the active persona. If no persona is specified, lists all available personas with summaries.",
|
|
20311
21424
|
{
|
|
@@ -20407,8 +21520,8 @@ function collectTools(marvinDir) {
|
|
|
20407
21520
|
const plugin = resolvePlugin(config2.methodology);
|
|
20408
21521
|
const registrations = plugin?.documentTypeRegistrations ?? [];
|
|
20409
21522
|
const store = new DocumentStore(marvinDir, registrations);
|
|
20410
|
-
const sourcesDir =
|
|
20411
|
-
const hasSourcesDir =
|
|
21523
|
+
const sourcesDir = path9.join(marvinDir, "sources");
|
|
21524
|
+
const hasSourcesDir = fs9.existsSync(sourcesDir);
|
|
20412
21525
|
const manifest = hasSourcesDir ? new SourceManifestManager(marvinDir) : void 0;
|
|
20413
21526
|
const pluginTools = plugin ? getPluginTools(plugin, store, marvinDir) : [];
|
|
20414
21527
|
const sessionStore = new SessionStore(marvinDir);
|
|
@@ -20416,7 +21529,7 @@ function collectTools(marvinDir) {
|
|
|
20416
21529
|
const allSkillIds = [...allSkills.keys()];
|
|
20417
21530
|
const codeSkillTools = getSkillTools(allSkillIds, allSkills, store);
|
|
20418
21531
|
const skillsWithActions = allSkillIds.map((id) => allSkills.get(id)).filter((s) => s.actions && s.actions.length > 0);
|
|
20419
|
-
const projectRoot =
|
|
21532
|
+
const projectRoot = path9.dirname(marvinDir);
|
|
20420
21533
|
const actionTools = createSkillActionTools(skillsWithActions, { store, marvinDir, projectRoot });
|
|
20421
21534
|
const allSkillRegs = collectSkillRegistrations(allSkillIds, allSkills);
|
|
20422
21535
|
const navGroups = buildNavGroups({
|
|
@@ -20489,9 +21602,9 @@ function parseProjectDir(argv) {
|
|
|
20489
21602
|
}
|
|
20490
21603
|
async function main() {
|
|
20491
21604
|
const projectDir = parseProjectDir(process.argv);
|
|
20492
|
-
const from = projectDir ?
|
|
21605
|
+
const from = projectDir ? path10.resolve(projectDir) : process.cwd();
|
|
20493
21606
|
const root = findProjectRoot(from);
|
|
20494
|
-
const marvinDir =
|
|
21607
|
+
const marvinDir = path10.join(root, ".marvin");
|
|
20495
21608
|
await startStdioServer({ marvinDir });
|
|
20496
21609
|
}
|
|
20497
21610
|
main().catch((err) => {
|