mrvn-cli 0.4.2 → 0.4.4

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