roadmap-skill 0.2.11 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,31 +1,40 @@
1
1
  #!/usr/bin/env node
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __esm = (fn, res) => function __init() {
5
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
- };
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
2
 
12
- // node_modules/tsup/assets/esm_shims.js
13
- import path from "path";
14
- import { fileURLToPath } from "url";
15
- var init_esm_shims = __esm({
16
- "node_modules/tsup/assets/esm_shims.js"() {
17
- "use strict";
18
- }
19
- });
3
+ // src/server.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ ListResourcesRequestSchema,
9
+ ReadResourceRequestSchema,
10
+ ListPromptsRequestSchema,
11
+ GetPromptRequestSchema
12
+ } from "@modelcontextprotocol/sdk/types.js";
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { zodToJsonSchema } from "zod-to-json-schema";
15
+
16
+ // src/tools/project-tools.ts
17
+ import { z } from "zod";
18
+
19
+ // src/storage/index.ts
20
+ import * as path3 from "path";
21
+
22
+ // src/utils/path-helpers.ts
23
+ import * as os from "os";
24
+ import * as path from "path";
25
+ function getStorageDir() {
26
+ const homeDir = os.homedir();
27
+ return path.join(homeDir, ".roadmap-skill", "projects");
28
+ }
20
29
 
21
30
  // src/utils/file-helpers.ts
22
- var file_helpers_exports = {};
23
- __export(file_helpers_exports, {
24
- ensureDir: () => ensureDir,
25
- readJsonFile: () => readJsonFile,
26
- writeJsonFile: () => writeJsonFile
27
- });
28
31
  import * as fs from "fs/promises";
32
+ import * as path2 from "path";
33
+ function buildTempFilePath(filePath) {
34
+ const fileName = path2.basename(filePath);
35
+ const tempSuffix = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
36
+ return path2.join(path2.dirname(filePath), `.${fileName}.${tempSuffix}.tmp`);
37
+ }
29
38
  async function readJsonFile(filePath) {
30
39
  try {
31
40
  const content = await fs.readFile(filePath, "utf-8");
@@ -38,10 +47,16 @@ async function readJsonFile(filePath) {
38
47
  }
39
48
  }
40
49
  async function writeJsonFile(filePath, data) {
50
+ const tempPath = buildTempFilePath(filePath);
41
51
  try {
42
52
  const content = JSON.stringify(data, null, 2);
43
- await fs.writeFile(filePath, content, "utf-8");
53
+ await fs.writeFile(tempPath, content, "utf-8");
54
+ await fs.rename(tempPath, filePath);
44
55
  } catch (error) {
56
+ try {
57
+ await fs.unlink(tempPath);
58
+ } catch {
59
+ }
45
60
  if (error instanceof Error) {
46
61
  throw new Error(`Failed to write JSON file ${filePath}: ${error.message}`);
47
62
  }
@@ -58,56 +73,44 @@ async function ensureDir(dirPath) {
58
73
  throw error;
59
74
  }
60
75
  }
61
- var init_file_helpers = __esm({
62
- "src/utils/file-helpers.ts"() {
63
- "use strict";
64
- init_esm_shims();
65
- }
66
- });
67
-
68
- // src/index.ts
69
- init_esm_shims();
70
-
71
- // src/server.ts
72
- init_esm_shims();
73
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
74
- import {
75
- CallToolRequestSchema,
76
- ListToolsRequestSchema,
77
- ListResourcesRequestSchema,
78
- ReadResourceRequestSchema,
79
- ListPromptsRequestSchema,
80
- GetPromptRequestSchema
81
- } from "@modelcontextprotocol/sdk/types.js";
82
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
83
- import { zodToJsonSchema } from "zod-to-json-schema";
84
-
85
- // src/tools/index.ts
86
- init_esm_shims();
87
-
88
- // src/tools/project-tools.ts
89
- init_esm_shims();
90
- import { z } from "zod";
91
-
92
- // src/storage/index.ts
93
- init_esm_shims();
94
- import * as path3 from "path";
95
-
96
- // src/utils/path-helpers.ts
97
- init_esm_shims();
98
- import * as os from "os";
99
- import * as path2 from "path";
100
- function getStorageDir() {
101
- const homeDir = os.homedir();
102
- return path2.join(homeDir, ".roadmap-skill", "projects");
103
- }
104
76
 
105
77
  // src/storage/index.ts
106
- init_file_helpers();
107
78
  var ProjectStorage = class {
108
79
  storageDir;
80
+ projectMutationQueues;
109
81
  constructor() {
110
82
  this.storageDir = getStorageDir();
83
+ this.projectMutationQueues = /* @__PURE__ */ new Map();
84
+ }
85
+ async runProjectMutation(projectId, operation) {
86
+ const previous = this.projectMutationQueues.get(projectId) ?? Promise.resolve();
87
+ let releaseQueue;
88
+ const current = new Promise((resolve) => {
89
+ releaseQueue = resolve;
90
+ });
91
+ this.projectMutationQueues.set(projectId, current);
92
+ try {
93
+ await previous;
94
+ return await operation();
95
+ } finally {
96
+ releaseQueue();
97
+ if (this.projectMutationQueues.get(projectId) === current) {
98
+ this.projectMutationQueues.delete(projectId);
99
+ }
100
+ }
101
+ }
102
+ async mutateProject(projectId, updater) {
103
+ return this.runProjectMutation(projectId, async () => {
104
+ const projectData = await this.readProject(projectId);
105
+ if (!projectData) {
106
+ return null;
107
+ }
108
+ const updateResult = await updater(projectData);
109
+ if (updateResult.shouldSave) {
110
+ await writeJsonFile(this.getFilePath(projectId), projectData);
111
+ }
112
+ return updateResult.result;
113
+ });
111
114
  }
112
115
  /**
113
116
  * Ensure the storage directory exists
@@ -148,7 +151,8 @@ var ProjectStorage = class {
148
151
  project,
149
152
  milestones: [],
150
153
  tasks: [],
151
- tags: []
154
+ tags: [],
155
+ dependencyViews: []
152
156
  };
153
157
  const filePath = this.getFilePath(projectId);
154
158
  await writeJsonFile(filePath, projectData);
@@ -162,7 +166,11 @@ var ProjectStorage = class {
162
166
  async readProject(projectId) {
163
167
  try {
164
168
  const filePath = this.getFilePath(projectId);
165
- return await readJsonFile(filePath);
169
+ const projectData = await readJsonFile(filePath);
170
+ return {
171
+ ...projectData,
172
+ dependencyViews: projectData.dependencyViews ?? []
173
+ };
166
174
  } catch (error) {
167
175
  if (error instanceof Error && error.message.includes("ENOENT")) {
168
176
  return null;
@@ -177,19 +185,18 @@ var ProjectStorage = class {
177
185
  * @returns The updated project data or null if not found
178
186
  */
179
187
  async updateProject(projectId, input) {
180
- const projectData = await this.readProject(projectId);
181
- if (!projectData) {
182
- return null;
183
- }
184
- const now = (/* @__PURE__ */ new Date()).toISOString();
185
- projectData.project = {
186
- ...projectData.project,
187
- ...input,
188
- updatedAt: now
189
- };
190
- const filePath = this.getFilePath(projectId);
191
- await writeJsonFile(filePath, projectData);
192
- return projectData;
188
+ return this.mutateProject(projectId, async (projectData) => {
189
+ const now = (/* @__PURE__ */ new Date()).toISOString();
190
+ projectData.project = {
191
+ ...projectData.project,
192
+ ...input,
193
+ updatedAt: now
194
+ };
195
+ return {
196
+ result: projectData,
197
+ shouldSave: true
198
+ };
199
+ });
193
200
  }
194
201
  /**
195
202
  * Delete a project by ID
@@ -197,17 +204,19 @@ var ProjectStorage = class {
197
204
  * @returns True if deleted, false if not found
198
205
  */
199
206
  async deleteProject(projectId) {
200
- try {
201
- const filePath = this.getFilePath(projectId);
202
- const fs3 = await import("fs/promises");
203
- await fs3.unlink(filePath);
204
- return true;
205
- } catch (error) {
206
- if (error instanceof Error && error.message.includes("ENOENT")) {
207
- return false;
207
+ return this.runProjectMutation(projectId, async () => {
208
+ try {
209
+ const filePath = this.getFilePath(projectId);
210
+ const fs3 = await import("fs/promises");
211
+ await fs3.unlink(filePath);
212
+ return true;
213
+ } catch (error) {
214
+ if (error instanceof Error && error.message.includes("ENOENT")) {
215
+ return false;
216
+ }
217
+ throw error;
208
218
  }
209
- throw error;
210
- }
219
+ });
211
220
  }
212
221
  /**
213
222
  * List all projects sorted by updatedAt (descending)
@@ -324,7 +333,9 @@ var ProjectStorage = class {
324
333
  continue;
325
334
  }
326
335
  const filePath = this.getFilePath(projectData.project.id);
327
- await writeJsonFile(filePath, projectData);
336
+ await this.runProjectMutation(projectData.project.id, async () => {
337
+ await writeJsonFile(filePath, projectData);
338
+ });
328
339
  imported++;
329
340
  } catch (error) {
330
341
  errors++;
@@ -345,7 +356,7 @@ var storage = new ProjectStorage();
345
356
  // src/tools/project-tools.ts
346
357
  var ProjectTypeEnum = z.enum(["roadmap", "skill-tree", "kanban"]);
347
358
  var ProjectStatusEnum = z.enum(["active", "completed", "archived"]);
348
- function toProjectSummary(project, taskCount, tags) {
359
+ function toProjectSummary(project, taskCount, dependencyViewCount, tags) {
349
360
  return {
350
361
  id: project.id,
351
362
  name: project.name,
@@ -353,6 +364,7 @@ function toProjectSummary(project, taskCount, tags) {
353
364
  status: project.status,
354
365
  targetDate: project.targetDate,
355
366
  taskCount,
367
+ dependencyViewCount,
356
368
  ...tags ? { tags } : {}
357
369
  };
358
370
  }
@@ -390,7 +402,7 @@ var createProjectTool = {
390
402
  const projectData = await storage.createProject(projectInput);
391
403
  return {
392
404
  success: true,
393
- data: input.verbose ? projectData : toProjectSummary(projectData.project, 0)
405
+ data: input.verbose ? projectData : toProjectSummary(projectData.project, 0, 0)
394
406
  };
395
407
  } catch (error) {
396
408
  return {
@@ -423,7 +435,12 @@ var listProjectsTool = {
423
435
  name: tag.name,
424
436
  color: tag.color
425
437
  }));
426
- return toProjectSummary(projectSummary.project, projectSummary.taskCount, tags);
438
+ return toProjectSummary(
439
+ projectSummary.project,
440
+ projectSummary.taskCount,
441
+ projectData?.dependencyViews?.length ?? 0,
442
+ tags
443
+ );
427
444
  })
428
445
  );
429
446
  return {
@@ -501,7 +518,11 @@ var updateProjectTool = {
501
518
  }
502
519
  return {
503
520
  success: true,
504
- data: verbose ? projectData : toProjectSummary(projectData.project, projectData.tasks.length)
521
+ data: verbose ? projectData : toProjectSummary(
522
+ projectData.project,
523
+ projectData.tasks.length,
524
+ projectData.dependencyViews.length
525
+ )
505
526
  };
506
527
  } catch (error) {
507
528
  return {
@@ -540,14 +561,9 @@ var deleteProjectTool = {
540
561
  };
541
562
 
542
563
  // src/tools/task-tools.ts
543
- init_esm_shims();
544
564
  import { z as z2 } from "zod";
545
565
 
546
- // src/services/index.ts
547
- init_esm_shims();
548
-
549
566
  // src/services/tag-service.ts
550
- init_esm_shims();
551
567
  function generateTagId() {
552
568
  return `tag_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
553
569
  }
@@ -597,47 +613,57 @@ var TagService = class {
597
613
  */
598
614
  async create(projectId, data) {
599
615
  try {
600
- const projectData = await this.storage.readProject(projectId);
601
- if (!projectData) {
602
- return {
603
- success: false,
604
- error: `Project with ID '${projectId}' not found`,
605
- code: "NOT_FOUND"
616
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
617
+ const existingTag = projectData.tags.find(
618
+ (t) => t.name.toLowerCase() === data.name.toLowerCase()
619
+ );
620
+ if (existingTag) {
621
+ return {
622
+ result: {
623
+ success: false,
624
+ error: `Tag with name '${data.name}' already exists in this project`,
625
+ code: "DUPLICATE_ERROR"
626
+ },
627
+ shouldSave: false
628
+ };
629
+ }
630
+ const resolvedColor = resolveTagColor(data.name, data.color);
631
+ if (!HEX_COLOR_PATTERN.test(resolvedColor)) {
632
+ return {
633
+ result: {
634
+ success: false,
635
+ error: `Color must be a valid hex code (e.g., #FF5733), received '${resolvedColor}'`,
636
+ code: "VALIDATION_ERROR"
637
+ },
638
+ shouldSave: false
639
+ };
640
+ }
641
+ const now = (/* @__PURE__ */ new Date()).toISOString();
642
+ const tag = {
643
+ id: generateTagId(),
644
+ name: data.name,
645
+ color: resolvedColor,
646
+ description: data.description || "",
647
+ createdAt: now
606
648
  };
607
- }
608
- const existingTag = projectData.tags.find(
609
- (t) => t.name.toLowerCase() === data.name.toLowerCase()
610
- );
611
- if (existingTag) {
649
+ projectData.tags.push(tag);
650
+ projectData.project.updatedAt = now;
612
651
  return {
613
- success: false,
614
- error: `Tag with name '${data.name}' already exists in this project`,
615
- code: "DUPLICATE_ERROR"
652
+ result: {
653
+ success: true,
654
+ data: tag
655
+ },
656
+ shouldSave: true
616
657
  };
617
- }
618
- const resolvedColor = resolveTagColor(data.name, data.color);
619
- if (!HEX_COLOR_PATTERN.test(resolvedColor)) {
658
+ });
659
+ if (result === null) {
620
660
  return {
621
661
  success: false,
622
- error: `Color must be a valid hex code (e.g., #FF5733), received '${resolvedColor}'`,
623
- code: "VALIDATION_ERROR"
662
+ error: `Project with ID '${projectId}' not found`,
663
+ code: "NOT_FOUND"
624
664
  };
625
665
  }
626
- const now = (/* @__PURE__ */ new Date()).toISOString();
627
- const tag = {
628
- id: generateTagId(),
629
- name: data.name,
630
- color: resolvedColor,
631
- description: data.description || "",
632
- createdAt: now
633
- };
634
- projectData.tags.push(tag);
635
- projectData.project.updatedAt = now;
636
- await this.saveProjectData(projectId, projectData);
637
- return {
638
- success: true,
639
- data: tag
640
- };
666
+ return result;
641
667
  } catch (error) {
642
668
  return {
643
669
  success: false,
@@ -691,63 +717,78 @@ var TagService = class {
691
717
  */
692
718
  async update(projectId, tagId, data) {
693
719
  try {
694
- const projectData = await this.storage.readProject(projectId);
695
- if (!projectData) {
696
- return {
697
- success: false,
698
- error: `Project with ID '${projectId}' not found`,
699
- code: "NOT_FOUND"
700
- };
701
- }
702
- const tagIndex = projectData.tags.findIndex((t) => t.id === tagId);
703
- if (tagIndex === -1) {
704
- return {
705
- success: false,
706
- error: `Tag with ID '${tagId}' not found in project '${projectId}'`,
707
- code: "NOT_FOUND"
708
- };
709
- }
710
- if (Object.keys(data).length === 0) {
711
- return {
712
- success: false,
713
- error: "At least one field to update is required",
714
- code: "VALIDATION_ERROR"
715
- };
716
- }
717
- if (data.name) {
718
- const existingTag2 = projectData.tags.find(
719
- (t) => t.name.toLowerCase() === data.name.toLowerCase() && t.id !== tagId
720
- );
721
- if (existingTag2) {
720
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
721
+ const tagIndex = projectData.tags.findIndex((t) => t.id === tagId);
722
+ if (tagIndex === -1) {
722
723
  return {
723
- success: false,
724
- error: `Tag with name '${data.name}' already exists in this project`,
725
- code: "DUPLICATE_ERROR"
724
+ result: {
725
+ success: false,
726
+ error: `Tag with ID '${tagId}' not found in project '${projectId}'`,
727
+ code: "NOT_FOUND"
728
+ },
729
+ shouldSave: false
726
730
  };
727
731
  }
728
- }
729
- if (data.color && !HEX_COLOR_PATTERN.test(data.color)) {
732
+ if (Object.keys(data).length === 0) {
733
+ return {
734
+ result: {
735
+ success: false,
736
+ error: "At least one field to update is required",
737
+ code: "VALIDATION_ERROR"
738
+ },
739
+ shouldSave: false
740
+ };
741
+ }
742
+ if (data.name) {
743
+ const existingTag2 = projectData.tags.find(
744
+ (t) => t.name.toLowerCase() === data.name.toLowerCase() && t.id !== tagId
745
+ );
746
+ if (existingTag2) {
747
+ return {
748
+ result: {
749
+ success: false,
750
+ error: `Tag with name '${data.name}' already exists in this project`,
751
+ code: "DUPLICATE_ERROR"
752
+ },
753
+ shouldSave: false
754
+ };
755
+ }
756
+ }
757
+ if (data.color && !HEX_COLOR_PATTERN.test(data.color)) {
758
+ return {
759
+ result: {
760
+ success: false,
761
+ error: `Color must be a valid hex code (e.g., #FF5733), received '${data.color}'`,
762
+ code: "VALIDATION_ERROR"
763
+ },
764
+ shouldSave: false
765
+ };
766
+ }
767
+ const existingTag = projectData.tags[tagIndex];
768
+ const updatedTag = {
769
+ ...existingTag,
770
+ ...data,
771
+ id: existingTag.id,
772
+ createdAt: existingTag.createdAt
773
+ };
774
+ projectData.tags[tagIndex] = updatedTag;
775
+ projectData.project.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
776
+ return {
777
+ result: {
778
+ success: true,
779
+ data: updatedTag
780
+ },
781
+ shouldSave: true
782
+ };
783
+ });
784
+ if (result === null) {
730
785
  return {
731
786
  success: false,
732
- error: `Color must be a valid hex code (e.g., #FF5733), received '${data.color}'`,
733
- code: "VALIDATION_ERROR"
787
+ error: `Project with ID '${projectId}' not found`,
788
+ code: "NOT_FOUND"
734
789
  };
735
790
  }
736
- const now = (/* @__PURE__ */ new Date()).toISOString();
737
- const existingTag = projectData.tags[tagIndex];
738
- const updatedTag = {
739
- ...existingTag,
740
- ...data,
741
- id: existingTag.id,
742
- createdAt: existingTag.createdAt
743
- };
744
- projectData.tags[tagIndex] = updatedTag;
745
- projectData.project.updatedAt = now;
746
- await this.saveProjectData(projectId, projectData);
747
- return {
748
- success: true,
749
- data: updatedTag
750
- };
791
+ return result;
751
792
  } catch (error) {
752
793
  return {
753
794
  success: false,
@@ -765,44 +806,51 @@ var TagService = class {
765
806
  */
766
807
  async delete(projectId, tagId) {
767
808
  try {
768
- const projectData = await this.storage.readProject(projectId);
769
- if (!projectData) {
809
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
810
+ const tagIndex = projectData.tags.findIndex((t) => t.id === tagId);
811
+ if (tagIndex === -1) {
812
+ return {
813
+ result: {
814
+ success: false,
815
+ error: `Tag with ID '${tagId}' not found in project '${projectId}'`,
816
+ code: "NOT_FOUND"
817
+ },
818
+ shouldSave: false
819
+ };
820
+ }
821
+ const tag = projectData.tags[tagIndex];
822
+ const now = (/* @__PURE__ */ new Date()).toISOString();
823
+ let tasksUpdated = 0;
824
+ for (const task of projectData.tasks) {
825
+ const tagIndexInTask = task.tags.indexOf(tagId);
826
+ if (tagIndexInTask !== -1) {
827
+ task.tags.splice(tagIndexInTask, 1);
828
+ task.updatedAt = now;
829
+ tasksUpdated++;
830
+ }
831
+ }
832
+ projectData.tags.splice(tagIndex, 1);
833
+ projectData.project.updatedAt = now;
770
834
  return {
771
- success: false,
772
- error: `Project with ID '${projectId}' not found`,
773
- code: "NOT_FOUND"
835
+ result: {
836
+ success: true,
837
+ data: {
838
+ deleted: true,
839
+ tag,
840
+ tasksUpdated
841
+ }
842
+ },
843
+ shouldSave: true
774
844
  };
775
- }
776
- const tagIndex = projectData.tags.findIndex((t) => t.id === tagId);
777
- if (tagIndex === -1) {
845
+ });
846
+ if (result === null) {
778
847
  return {
779
848
  success: false,
780
- error: `Tag with ID '${tagId}' not found in project '${projectId}'`,
849
+ error: `Project with ID '${projectId}' not found`,
781
850
  code: "NOT_FOUND"
782
851
  };
783
852
  }
784
- const tag = projectData.tags[tagIndex];
785
- const now = (/* @__PURE__ */ new Date()).toISOString();
786
- let tasksUpdated = 0;
787
- for (const task of projectData.tasks) {
788
- const tagIndexInTask = task.tags.indexOf(tagId);
789
- if (tagIndexInTask !== -1) {
790
- task.tags.splice(tagIndexInTask, 1);
791
- task.updatedAt = now;
792
- tasksUpdated++;
793
- }
794
- }
795
- projectData.tags.splice(tagIndex, 1);
796
- projectData.project.updatedAt = now;
797
- await this.saveProjectData(projectId, projectData);
798
- return {
799
- success: true,
800
- data: {
801
- deleted: true,
802
- tag,
803
- tasksUpdated
804
- }
805
- };
853
+ return result;
806
854
  } catch (error) {
807
855
  return {
808
856
  success: false,
@@ -854,21 +902,9 @@ var TagService = class {
854
902
  };
855
903
  }
856
904
  }
857
- /**
858
- * Helper method to save project data
859
- * @param projectId - The project ID
860
- * @param projectData - The project data to save
861
- */
862
- async saveProjectData(projectId, projectData) {
863
- const filePath = this.storage.getFilePath(projectId);
864
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
865
- await writeJsonFile2(filePath, projectData);
866
- }
867
905
  };
868
906
 
869
907
  // src/services/task-service.ts
870
- init_esm_shims();
871
- init_file_helpers();
872
908
  function generateTaskId() {
873
909
  return `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
874
910
  }
@@ -897,46 +933,52 @@ var TaskService = {
897
933
  */
898
934
  async create(projectId, data) {
899
935
  try {
900
- const projectData = await storage.readProject(projectId);
901
- if (!projectData) {
936
+ const result = await storage.mutateProject(projectId, async (projectData) => {
937
+ const incomingTagIds = data.tags ?? [];
938
+ const invalidTagIds = findInvalidTagIds(projectData, incomingTagIds);
939
+ if (invalidTagIds.length > 0) {
940
+ return {
941
+ result: {
942
+ success: false,
943
+ error: `Invalid tag IDs for project '${projectId}': ${invalidTagIds.join(", ")}`,
944
+ code: "VALIDATION_ERROR"
945
+ },
946
+ shouldSave: false
947
+ };
948
+ }
949
+ const now = (/* @__PURE__ */ new Date()).toISOString();
950
+ const task = {
951
+ id: generateTaskId(),
952
+ projectId,
953
+ title: data.title,
954
+ description: data.description,
955
+ status: "todo",
956
+ priority: data.priority ?? "medium",
957
+ tags: incomingTagIds,
958
+ dueDate: data.dueDate ?? null,
959
+ assignee: data.assignee ?? null,
960
+ createdAt: now,
961
+ updatedAt: now,
962
+ completedAt: null
963
+ };
964
+ projectData.tasks.push(task);
965
+ projectData.project.updatedAt = now;
902
966
  return {
903
- success: false,
904
- error: `Project with ID '${projectId}' not found`,
905
- code: "NOT_FOUND"
967
+ result: {
968
+ success: true,
969
+ data: task
970
+ },
971
+ shouldSave: true
906
972
  };
907
- }
908
- const incomingTagIds = data.tags ?? [];
909
- const invalidTagIds = findInvalidTagIds(projectData, incomingTagIds);
910
- if (invalidTagIds.length > 0) {
973
+ });
974
+ if (result === null) {
911
975
  return {
912
976
  success: false,
913
- error: `Invalid tag IDs for project '${projectId}': ${invalidTagIds.join(", ")}`,
914
- code: "VALIDATION_ERROR"
977
+ error: `Project with ID '${projectId}' not found`,
978
+ code: "NOT_FOUND"
915
979
  };
916
980
  }
917
- const now = (/* @__PURE__ */ new Date()).toISOString();
918
- const task = {
919
- id: generateTaskId(),
920
- projectId,
921
- title: data.title,
922
- description: data.description,
923
- status: "todo",
924
- priority: data.priority ?? "medium",
925
- tags: incomingTagIds,
926
- dueDate: data.dueDate ?? null,
927
- assignee: data.assignee ?? null,
928
- createdAt: now,
929
- updatedAt: now,
930
- completedAt: null
931
- };
932
- projectData.tasks.push(task);
933
- projectData.project.updatedAt = now;
934
- const filePath = storage.getFilePath(projectId);
935
- await writeJsonFile(filePath, projectData);
936
- return {
937
- success: true,
938
- data: task
939
- };
981
+ return result;
940
982
  } catch (error) {
941
983
  return {
942
984
  success: false,
@@ -991,65 +1033,77 @@ var TaskService = {
991
1033
  */
992
1034
  async update(projectId, taskId, data) {
993
1035
  try {
994
- const projectData = await storage.readProject(projectId);
995
- if (!projectData) {
996
- return {
997
- success: false,
998
- error: `Project with ID '${projectId}' not found`,
999
- code: "NOT_FOUND"
1000
- };
1001
- }
1002
- const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
1003
- if (taskIndex === -1) {
1004
- return {
1005
- success: false,
1006
- error: `Task with ID '${taskId}' not found in project '${projectId}'`,
1007
- code: "NOT_FOUND"
1008
- };
1009
- }
1010
- const updateKeys = Object.keys(data);
1011
- if (updateKeys.length === 0) {
1012
- return {
1013
- success: false,
1014
- error: "At least one field to update is required",
1015
- code: "VALIDATION_ERROR"
1016
- };
1017
- }
1018
- if (data.tags) {
1019
- const invalidTagIds = findInvalidTagIds(projectData, data.tags);
1020
- if (invalidTagIds.length > 0) {
1036
+ const result = await storage.mutateProject(projectId, async (projectData) => {
1037
+ const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
1038
+ if (taskIndex === -1) {
1021
1039
  return {
1022
- success: false,
1023
- error: `Invalid tag IDs for project '${projectId}': ${invalidTagIds.join(", ")}`,
1024
- code: "VALIDATION_ERROR"
1040
+ result: {
1041
+ success: false,
1042
+ error: `Task with ID '${taskId}' not found in project '${projectId}'`,
1043
+ code: "NOT_FOUND"
1044
+ },
1045
+ shouldSave: false
1025
1046
  };
1026
1047
  }
1048
+ const updateKeys = Object.keys(data);
1049
+ if (updateKeys.length === 0) {
1050
+ return {
1051
+ result: {
1052
+ success: false,
1053
+ error: "At least one field to update is required",
1054
+ code: "VALIDATION_ERROR"
1055
+ },
1056
+ shouldSave: false
1057
+ };
1058
+ }
1059
+ if (data.tags) {
1060
+ const invalidTagIds = findInvalidTagIds(projectData, data.tags);
1061
+ if (invalidTagIds.length > 0) {
1062
+ return {
1063
+ result: {
1064
+ success: false,
1065
+ error: `Invalid tag IDs for project '${projectId}': ${invalidTagIds.join(", ")}`,
1066
+ code: "VALIDATION_ERROR"
1067
+ },
1068
+ shouldSave: false
1069
+ };
1070
+ }
1071
+ }
1072
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1073
+ const existingTask = projectData.tasks[taskIndex];
1074
+ const completedAt = calculateCompletedAt(
1075
+ existingTask.status,
1076
+ data.status,
1077
+ existingTask.completedAt,
1078
+ now
1079
+ );
1080
+ const updatedTask = {
1081
+ ...existingTask,
1082
+ ...data,
1083
+ id: existingTask.id,
1084
+ projectId: existingTask.projectId,
1085
+ createdAt: existingTask.createdAt,
1086
+ updatedAt: now,
1087
+ completedAt
1088
+ };
1089
+ projectData.tasks[taskIndex] = updatedTask;
1090
+ projectData.project.updatedAt = now;
1091
+ return {
1092
+ result: {
1093
+ success: true,
1094
+ data: updatedTask
1095
+ },
1096
+ shouldSave: true
1097
+ };
1098
+ });
1099
+ if (result === null) {
1100
+ return {
1101
+ success: false,
1102
+ error: `Project with ID '${projectId}' not found`,
1103
+ code: "NOT_FOUND"
1104
+ };
1027
1105
  }
1028
- const now = (/* @__PURE__ */ new Date()).toISOString();
1029
- const existingTask = projectData.tasks[taskIndex];
1030
- const completedAt = calculateCompletedAt(
1031
- existingTask.status,
1032
- data.status,
1033
- existingTask.completedAt,
1034
- now
1035
- );
1036
- const updatedTask = {
1037
- ...existingTask,
1038
- ...data,
1039
- id: existingTask.id,
1040
- projectId: existingTask.projectId,
1041
- createdAt: existingTask.createdAt,
1042
- updatedAt: now,
1043
- completedAt
1044
- };
1045
- projectData.tasks[taskIndex] = updatedTask;
1046
- projectData.project.updatedAt = now;
1047
- const filePath = storage.getFilePath(projectId);
1048
- await writeJsonFile(filePath, projectData);
1049
- return {
1050
- success: true,
1051
- data: updatedTask
1052
- };
1106
+ return result;
1053
1107
  } catch (error) {
1054
1108
  return {
1055
1109
  success: false,
@@ -1066,31 +1120,37 @@ var TaskService = {
1066
1120
  */
1067
1121
  async delete(projectId, taskId) {
1068
1122
  try {
1069
- const projectData = await storage.readProject(projectId);
1070
- if (!projectData) {
1123
+ const result = await storage.mutateProject(projectId, async (projectData) => {
1124
+ const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
1125
+ if (taskIndex === -1) {
1126
+ return {
1127
+ result: {
1128
+ success: false,
1129
+ error: `Task with ID '${taskId}' not found in project '${projectId}'`,
1130
+ code: "NOT_FOUND"
1131
+ },
1132
+ shouldSave: false
1133
+ };
1134
+ }
1135
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1136
+ projectData.tasks.splice(taskIndex, 1);
1137
+ projectData.project.updatedAt = now;
1071
1138
  return {
1072
- success: false,
1073
- error: `Project with ID '${projectId}' not found`,
1074
- code: "NOT_FOUND"
1139
+ result: {
1140
+ success: true,
1141
+ data: void 0
1142
+ },
1143
+ shouldSave: true
1075
1144
  };
1076
- }
1077
- const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
1078
- if (taskIndex === -1) {
1145
+ });
1146
+ if (result === null) {
1079
1147
  return {
1080
1148
  success: false,
1081
- error: `Task with ID '${taskId}' not found in project '${projectId}'`,
1149
+ error: `Project with ID '${projectId}' not found`,
1082
1150
  code: "NOT_FOUND"
1083
1151
  };
1084
1152
  }
1085
- const now = (/* @__PURE__ */ new Date()).toISOString();
1086
- projectData.tasks.splice(taskIndex, 1);
1087
- projectData.project.updatedAt = now;
1088
- const filePath = storage.getFilePath(projectId);
1089
- await writeJsonFile(filePath, projectData);
1090
- return {
1091
- success: true,
1092
- data: void 0
1093
- };
1153
+ return result;
1094
1154
  } catch (error) {
1095
1155
  return {
1096
1156
  success: false,
@@ -1138,7 +1198,996 @@ var TaskService = {
1138
1198
  */
1139
1199
  async batchUpdate(projectId, taskIds, data) {
1140
1200
  try {
1141
- const projectData = await storage.readProject(projectId);
1201
+ const result = await storage.mutateProject(projectId, async (projectData) => {
1202
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1203
+ const updatedTasks = [];
1204
+ const notFoundIds = [];
1205
+ for (const taskId of taskIds) {
1206
+ const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
1207
+ if (taskIndex === -1) {
1208
+ notFoundIds.push(taskId);
1209
+ continue;
1210
+ }
1211
+ const existingTask = projectData.tasks[taskIndex];
1212
+ let updatedTags = existingTask.tags ?? [];
1213
+ if (data.tags) {
1214
+ const invalidTagIds = findInvalidTagIds(projectData, data.tags);
1215
+ if (invalidTagIds.length > 0) {
1216
+ return {
1217
+ result: {
1218
+ success: false,
1219
+ error: `Invalid tag IDs for project '${projectId}': ${invalidTagIds.join(", ")}`,
1220
+ code: "VALIDATION_ERROR"
1221
+ },
1222
+ shouldSave: false
1223
+ };
1224
+ }
1225
+ }
1226
+ if (data.tags) {
1227
+ const existingTags = existingTask.tags ?? [];
1228
+ switch (data.tagOperation) {
1229
+ case "add":
1230
+ updatedTags = [.../* @__PURE__ */ new Set([...existingTags, ...data.tags])];
1231
+ break;
1232
+ case "remove":
1233
+ updatedTags = existingTags.filter((tag) => !data.tags.includes(tag));
1234
+ break;
1235
+ case "replace":
1236
+ default:
1237
+ updatedTags = data.tags;
1238
+ break;
1239
+ }
1240
+ }
1241
+ const completedAt = calculateCompletedAt(
1242
+ existingTask.status,
1243
+ data.status,
1244
+ existingTask.completedAt,
1245
+ now
1246
+ );
1247
+ const updatedTask = {
1248
+ ...existingTask,
1249
+ ...data.status && { status: data.status },
1250
+ ...data.priority && { priority: data.priority },
1251
+ tags: updatedTags,
1252
+ updatedAt: now,
1253
+ completedAt
1254
+ };
1255
+ projectData.tasks[taskIndex] = updatedTask;
1256
+ updatedTasks.push(updatedTask);
1257
+ }
1258
+ if (updatedTasks.length === 0) {
1259
+ return {
1260
+ result: {
1261
+ success: false,
1262
+ error: "No tasks were found to update",
1263
+ code: "NOT_FOUND"
1264
+ },
1265
+ shouldSave: false
1266
+ };
1267
+ }
1268
+ projectData.project.updatedAt = now;
1269
+ return {
1270
+ result: {
1271
+ success: true,
1272
+ data: {
1273
+ updatedTasks,
1274
+ updatedCount: updatedTasks.length,
1275
+ notFoundIds: notFoundIds.length > 0 ? notFoundIds : void 0
1276
+ }
1277
+ },
1278
+ shouldSave: true
1279
+ };
1280
+ });
1281
+ if (result === null) {
1282
+ return {
1283
+ success: false,
1284
+ error: `Project with ID '${projectId}' not found`,
1285
+ code: "NOT_FOUND"
1286
+ };
1287
+ }
1288
+ return result;
1289
+ } catch (error) {
1290
+ return {
1291
+ success: false,
1292
+ error: error instanceof Error ? error.message : "Failed to batch update tasks",
1293
+ code: "INTERNAL_ERROR"
1294
+ };
1295
+ }
1296
+ }
1297
+ };
1298
+
1299
+ // src/services/dependency-view-service.ts
1300
+ function generateDependencyViewId() {
1301
+ return `depview_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1302
+ }
1303
+ function generateDependencyViewEdgeId() {
1304
+ return `depedge_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1305
+ }
1306
+ function getDependencyViews(projectData) {
1307
+ if (!projectData.dependencyViews) {
1308
+ projectData.dependencyViews = [];
1309
+ }
1310
+ return projectData.dependencyViews;
1311
+ }
1312
+ function findView(projectData, viewId) {
1313
+ return getDependencyViews(projectData).find((view) => view.id === viewId);
1314
+ }
1315
+ function cloneView(view) {
1316
+ return {
1317
+ ...view,
1318
+ nodes: view.nodes.map((node) => ({ ...node })),
1319
+ edges: view.edges.map((edge) => ({ ...edge }))
1320
+ };
1321
+ }
1322
+ function applyNodeUpdate(node, data) {
1323
+ if (data.x !== void 0) {
1324
+ node.x = data.x;
1325
+ }
1326
+ if (data.y !== void 0) {
1327
+ node.y = data.y;
1328
+ }
1329
+ if (data.collapsed !== void 0) {
1330
+ node.collapsed = data.collapsed;
1331
+ }
1332
+ if (data.note !== void 0) {
1333
+ node.note = data.note;
1334
+ }
1335
+ }
1336
+ function createMutationChanges(partial) {
1337
+ return {
1338
+ addedNodeIds: partial?.addedNodeIds ?? [],
1339
+ updatedNodeIds: partial?.updatedNodeIds ?? [],
1340
+ removedNodeIds: partial?.removedNodeIds ?? [],
1341
+ addedEdgeIds: partial?.addedEdgeIds ?? [],
1342
+ updatedEdgeIds: partial?.updatedEdgeIds ?? [],
1343
+ removedEdgeIds: partial?.removedEdgeIds ?? []
1344
+ };
1345
+ }
1346
+ function buildAdjacency(view) {
1347
+ const adjacency = /* @__PURE__ */ new Map();
1348
+ for (const node of view.nodes) {
1349
+ adjacency.set(node.taskId, []);
1350
+ }
1351
+ for (const edge of view.edges) {
1352
+ const neighbors = adjacency.get(edge.fromTaskId);
1353
+ if (neighbors) {
1354
+ neighbors.push(edge.toTaskId);
1355
+ }
1356
+ }
1357
+ return adjacency;
1358
+ }
1359
+ function canReach(adjacency, startId, targetId) {
1360
+ if (startId === targetId) {
1361
+ return true;
1362
+ }
1363
+ const visited = /* @__PURE__ */ new Set();
1364
+ const stack = [startId];
1365
+ while (stack.length > 0) {
1366
+ const current = stack.pop();
1367
+ if (!current || visited.has(current)) {
1368
+ continue;
1369
+ }
1370
+ visited.add(current);
1371
+ const neighbors = adjacency.get(current) ?? [];
1372
+ for (const neighbor of neighbors) {
1373
+ if (neighbor === targetId) {
1374
+ return true;
1375
+ }
1376
+ if (!visited.has(neighbor)) {
1377
+ stack.push(neighbor);
1378
+ }
1379
+ }
1380
+ }
1381
+ return false;
1382
+ }
1383
+ function analyzeDependencyView(view) {
1384
+ const nodeIds = view.nodes.map((node) => node.taskId);
1385
+ const adjacency = buildAdjacency(view);
1386
+ const indegree = new Map(nodeIds.map((nodeId) => [nodeId, 0]));
1387
+ const outgoing = new Map(nodeIds.map((nodeId) => [nodeId, 0]));
1388
+ for (const edge of view.edges) {
1389
+ indegree.set(edge.toTaskId, (indegree.get(edge.toTaskId) ?? 0) + 1);
1390
+ outgoing.set(edge.fromTaskId, (outgoing.get(edge.fromTaskId) ?? 0) + 1);
1391
+ }
1392
+ const queue = nodeIds.filter((nodeId) => (indegree.get(nodeId) ?? 0) === 0);
1393
+ const indegreeCopy = new Map(indegree);
1394
+ const topologicalOrder = [];
1395
+ const layerByTask = /* @__PURE__ */ new Map();
1396
+ while (queue.length > 0) {
1397
+ const current = queue.shift();
1398
+ if (!current) {
1399
+ continue;
1400
+ }
1401
+ topologicalOrder.push(current);
1402
+ const currentLayer = layerByTask.get(current) ?? 0;
1403
+ const neighbors = adjacency.get(current) ?? [];
1404
+ for (const neighbor of neighbors) {
1405
+ layerByTask.set(neighbor, Math.max(layerByTask.get(neighbor) ?? 0, currentLayer + 1));
1406
+ const nextIndegree = (indegreeCopy.get(neighbor) ?? 0) - 1;
1407
+ indegreeCopy.set(neighbor, nextIndegree);
1408
+ if (nextIndegree === 0) {
1409
+ queue.push(neighbor);
1410
+ }
1411
+ }
1412
+ }
1413
+ const layers = [];
1414
+ for (const nodeId of topologicalOrder) {
1415
+ const layerIndex = layerByTask.get(nodeId) ?? 0;
1416
+ if (!layers[layerIndex]) {
1417
+ layers[layerIndex] = [];
1418
+ }
1419
+ layers[layerIndex].push(nodeId);
1420
+ }
1421
+ return {
1422
+ viewId: view.id,
1423
+ revision: view.revision,
1424
+ topologicalOrder,
1425
+ layers,
1426
+ readyTaskIds: nodeIds.filter((nodeId) => (indegree.get(nodeId) ?? 0) === 0),
1427
+ blockedTaskIds: nodeIds.filter((nodeId) => (indegree.get(nodeId) ?? 0) > 0),
1428
+ roots: nodeIds.filter((nodeId) => (indegree.get(nodeId) ?? 0) === 0),
1429
+ leaves: nodeIds.filter((nodeId) => (outgoing.get(nodeId) ?? 0) === 0),
1430
+ isolatedTaskIds: nodeIds.filter(
1431
+ (nodeId) => (indegree.get(nodeId) ?? 0) === 0 && (outgoing.get(nodeId) ?? 0) === 0
1432
+ )
1433
+ };
1434
+ }
1435
+ var DependencyViewService = class {
1436
+ storage;
1437
+ constructor(storage2) {
1438
+ this.storage = storage2;
1439
+ }
1440
+ async create(projectId, data) {
1441
+ try {
1442
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
1443
+ const views = getDependencyViews(projectData);
1444
+ const name = data.name.trim();
1445
+ if (!name) {
1446
+ return {
1447
+ result: {
1448
+ success: false,
1449
+ error: "Dependency view name is required",
1450
+ code: "VALIDATION_ERROR"
1451
+ },
1452
+ shouldSave: false
1453
+ };
1454
+ }
1455
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1456
+ const view = {
1457
+ id: generateDependencyViewId(),
1458
+ projectId,
1459
+ name,
1460
+ description: data.description,
1461
+ dimension: data.dimension ?? null,
1462
+ createdAt: now,
1463
+ updatedAt: now,
1464
+ revision: 1,
1465
+ nodes: [],
1466
+ edges: []
1467
+ };
1468
+ views.push(view);
1469
+ projectData.project.updatedAt = now;
1470
+ return {
1471
+ result: { success: true, data: view },
1472
+ shouldSave: true
1473
+ };
1474
+ });
1475
+ if (result === null) {
1476
+ return {
1477
+ success: false,
1478
+ error: `Project with ID '${projectId}' not found`,
1479
+ code: "NOT_FOUND"
1480
+ };
1481
+ }
1482
+ return result;
1483
+ } catch (error) {
1484
+ return {
1485
+ success: false,
1486
+ error: error instanceof Error ? error.message : "Failed to create dependency view",
1487
+ code: "INTERNAL_ERROR"
1488
+ };
1489
+ }
1490
+ }
1491
+ async list(projectId) {
1492
+ try {
1493
+ const projectData = await this.storage.readProject(projectId);
1494
+ if (!projectData) {
1495
+ return {
1496
+ success: false,
1497
+ error: `Project with ID '${projectId}' not found`,
1498
+ code: "NOT_FOUND"
1499
+ };
1500
+ }
1501
+ return { success: true, data: getDependencyViews(projectData).map(cloneView) };
1502
+ } catch (error) {
1503
+ return {
1504
+ success: false,
1505
+ error: error instanceof Error ? error.message : "Failed to list dependency views",
1506
+ code: "INTERNAL_ERROR"
1507
+ };
1508
+ }
1509
+ }
1510
+ async get(projectId, viewId) {
1511
+ try {
1512
+ const projectData = await this.storage.readProject(projectId);
1513
+ if (!projectData) {
1514
+ return {
1515
+ success: false,
1516
+ error: `Project with ID '${projectId}' not found`,
1517
+ code: "NOT_FOUND"
1518
+ };
1519
+ }
1520
+ const view = findView(projectData, viewId);
1521
+ if (!view) {
1522
+ return {
1523
+ success: false,
1524
+ error: `Dependency view with ID '${viewId}' not found in project '${projectId}'`,
1525
+ code: "NOT_FOUND"
1526
+ };
1527
+ }
1528
+ return { success: true, data: cloneView(view) };
1529
+ } catch (error) {
1530
+ return {
1531
+ success: false,
1532
+ error: error instanceof Error ? error.message : "Failed to get dependency view",
1533
+ code: "INTERNAL_ERROR"
1534
+ };
1535
+ }
1536
+ }
1537
+ async update(projectId, viewId, data) {
1538
+ try {
1539
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
1540
+ if (Object.keys(data).length === 0) {
1541
+ return {
1542
+ result: {
1543
+ success: false,
1544
+ error: "At least one field to update is required",
1545
+ code: "VALIDATION_ERROR"
1546
+ },
1547
+ shouldSave: false
1548
+ };
1549
+ }
1550
+ const view = findView(projectData, viewId);
1551
+ if (!view) {
1552
+ return {
1553
+ result: {
1554
+ success: false,
1555
+ error: `Dependency view with ID '${viewId}' not found in project '${projectId}'`,
1556
+ code: "NOT_FOUND"
1557
+ },
1558
+ shouldSave: false
1559
+ };
1560
+ }
1561
+ if (data.name !== void 0 && !data.name.trim()) {
1562
+ return {
1563
+ result: {
1564
+ success: false,
1565
+ error: "Dependency view name cannot be empty",
1566
+ code: "VALIDATION_ERROR"
1567
+ },
1568
+ shouldSave: false
1569
+ };
1570
+ }
1571
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1572
+ view.name = data.name?.trim() ?? view.name;
1573
+ if (data.description !== void 0) {
1574
+ view.description = data.description;
1575
+ }
1576
+ if (data.dimension !== void 0) {
1577
+ view.dimension = data.dimension;
1578
+ }
1579
+ view.updatedAt = now;
1580
+ view.revision += 1;
1581
+ projectData.project.updatedAt = now;
1582
+ return {
1583
+ result: { success: true, data: cloneView(view) },
1584
+ shouldSave: true
1585
+ };
1586
+ });
1587
+ if (result === null) {
1588
+ return {
1589
+ success: false,
1590
+ error: `Project with ID '${projectId}' not found`,
1591
+ code: "NOT_FOUND"
1592
+ };
1593
+ }
1594
+ return result;
1595
+ } catch (error) {
1596
+ return {
1597
+ success: false,
1598
+ error: error instanceof Error ? error.message : "Failed to update dependency view",
1599
+ code: "INTERNAL_ERROR"
1600
+ };
1601
+ }
1602
+ }
1603
+ async delete(projectId, viewId) {
1604
+ try {
1605
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
1606
+ const views = getDependencyViews(projectData);
1607
+ const viewIndex = views.findIndex((view) => view.id === viewId);
1608
+ if (viewIndex === -1) {
1609
+ return {
1610
+ result: {
1611
+ success: false,
1612
+ error: `Dependency view with ID '${viewId}' not found in project '${projectId}'`,
1613
+ code: "NOT_FOUND"
1614
+ },
1615
+ shouldSave: false
1616
+ };
1617
+ }
1618
+ views.splice(viewIndex, 1);
1619
+ projectData.project.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1620
+ return {
1621
+ result: { success: true, data: void 0 },
1622
+ shouldSave: true
1623
+ };
1624
+ });
1625
+ if (result === null) {
1626
+ return {
1627
+ success: false,
1628
+ error: `Project with ID '${projectId}' not found`,
1629
+ code: "NOT_FOUND"
1630
+ };
1631
+ }
1632
+ return result;
1633
+ } catch (error) {
1634
+ return {
1635
+ success: false,
1636
+ error: error instanceof Error ? error.message : "Failed to delete dependency view",
1637
+ code: "INTERNAL_ERROR"
1638
+ };
1639
+ }
1640
+ }
1641
+ async addNode(projectId, viewId, data) {
1642
+ try {
1643
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
1644
+ const view = findView(projectData, viewId);
1645
+ if (!view) {
1646
+ return {
1647
+ result: {
1648
+ success: false,
1649
+ error: `Dependency view with ID '${viewId}' not found in project '${projectId}'`,
1650
+ code: "NOT_FOUND"
1651
+ },
1652
+ shouldSave: false
1653
+ };
1654
+ }
1655
+ const task = projectData.tasks.find((item) => item.id === data.taskId);
1656
+ if (!task) {
1657
+ return {
1658
+ result: {
1659
+ success: false,
1660
+ error: `Task with ID '${data.taskId}' not found in project '${projectId}'`,
1661
+ code: "NOT_FOUND"
1662
+ },
1663
+ shouldSave: false
1664
+ };
1665
+ }
1666
+ if (view.nodes.some((node2) => node2.taskId === data.taskId)) {
1667
+ return {
1668
+ result: {
1669
+ success: false,
1670
+ error: `Task '${data.taskId}' is already in dependency view '${viewId}'`,
1671
+ code: "DUPLICATE_ERROR"
1672
+ },
1673
+ shouldSave: false
1674
+ };
1675
+ }
1676
+ const node = {
1677
+ taskId: data.taskId,
1678
+ x: data.x ?? 0,
1679
+ y: data.y ?? 0,
1680
+ collapsed: data.collapsed ?? false,
1681
+ note: data.note ?? null
1682
+ };
1683
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1684
+ view.nodes.push(node);
1685
+ view.updatedAt = now;
1686
+ view.revision += 1;
1687
+ projectData.project.updatedAt = now;
1688
+ return {
1689
+ result: {
1690
+ success: true,
1691
+ data: {
1692
+ view: cloneView(view),
1693
+ changes: createMutationChanges({ addedNodeIds: [data.taskId] })
1694
+ }
1695
+ },
1696
+ shouldSave: true
1697
+ };
1698
+ });
1699
+ if (result === null) {
1700
+ return {
1701
+ success: false,
1702
+ error: `Project with ID '${projectId}' not found`,
1703
+ code: "NOT_FOUND"
1704
+ };
1705
+ }
1706
+ return result;
1707
+ } catch (error) {
1708
+ return {
1709
+ success: false,
1710
+ error: error instanceof Error ? error.message : "Failed to add task to dependency view",
1711
+ code: "INTERNAL_ERROR"
1712
+ };
1713
+ }
1714
+ }
1715
+ async updateNode(projectId, viewId, taskId, data) {
1716
+ try {
1717
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
1718
+ if (Object.keys(data).length === 0) {
1719
+ return {
1720
+ result: {
1721
+ success: false,
1722
+ error: "At least one node field to update is required",
1723
+ code: "VALIDATION_ERROR"
1724
+ },
1725
+ shouldSave: false
1726
+ };
1727
+ }
1728
+ const view = findView(projectData, viewId);
1729
+ if (!view) {
1730
+ return {
1731
+ result: {
1732
+ success: false,
1733
+ error: `Dependency view with ID '${viewId}' not found in project '${projectId}'`,
1734
+ code: "NOT_FOUND"
1735
+ },
1736
+ shouldSave: false
1737
+ };
1738
+ }
1739
+ const node = view.nodes.find((item) => item.taskId === taskId);
1740
+ if (!node) {
1741
+ return {
1742
+ result: {
1743
+ success: false,
1744
+ error: `Task '${taskId}' is not present in dependency view '${viewId}'`,
1745
+ code: "NOT_FOUND"
1746
+ },
1747
+ shouldSave: false
1748
+ };
1749
+ }
1750
+ applyNodeUpdate(node, data);
1751
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1752
+ view.updatedAt = now;
1753
+ view.revision += 1;
1754
+ projectData.project.updatedAt = now;
1755
+ return {
1756
+ result: {
1757
+ success: true,
1758
+ data: {
1759
+ view: cloneView(view),
1760
+ changes: createMutationChanges({ updatedNodeIds: [taskId] })
1761
+ }
1762
+ },
1763
+ shouldSave: true
1764
+ };
1765
+ });
1766
+ if (result === null) {
1767
+ return {
1768
+ success: false,
1769
+ error: `Project with ID '${projectId}' not found`,
1770
+ code: "NOT_FOUND"
1771
+ };
1772
+ }
1773
+ return result;
1774
+ } catch (error) {
1775
+ return {
1776
+ success: false,
1777
+ error: error instanceof Error ? error.message : "Failed to update dependency view node",
1778
+ code: "INTERNAL_ERROR"
1779
+ };
1780
+ }
1781
+ }
1782
+ async batchUpdateNodes(projectId, viewId, data) {
1783
+ try {
1784
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
1785
+ if (data.nodes.length === 0) {
1786
+ return {
1787
+ result: {
1788
+ success: false,
1789
+ error: "At least one node update is required",
1790
+ code: "VALIDATION_ERROR"
1791
+ },
1792
+ shouldSave: false
1793
+ };
1794
+ }
1795
+ const view = findView(projectData, viewId);
1796
+ if (!view) {
1797
+ return {
1798
+ result: {
1799
+ success: false,
1800
+ error: `Dependency view with ID '${viewId}' not found in project '${projectId}'`,
1801
+ code: "NOT_FOUND"
1802
+ },
1803
+ shouldSave: false
1804
+ };
1805
+ }
1806
+ for (const nodeUpdate of data.nodes) {
1807
+ const node = view.nodes.find((item) => item.taskId === nodeUpdate.taskId);
1808
+ if (!node) {
1809
+ return {
1810
+ result: {
1811
+ success: false,
1812
+ error: `Task '${nodeUpdate.taskId}' is not present in dependency view '${viewId}'`,
1813
+ code: "NOT_FOUND"
1814
+ },
1815
+ shouldSave: false
1816
+ };
1817
+ }
1818
+ applyNodeUpdate(node, nodeUpdate);
1819
+ }
1820
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1821
+ view.updatedAt = now;
1822
+ view.revision += 1;
1823
+ projectData.project.updatedAt = now;
1824
+ return {
1825
+ result: {
1826
+ success: true,
1827
+ data: {
1828
+ view: cloneView(view),
1829
+ changes: createMutationChanges({
1830
+ updatedNodeIds: [...new Set(data.nodes.map((node) => node.taskId))]
1831
+ })
1832
+ }
1833
+ },
1834
+ shouldSave: true
1835
+ };
1836
+ });
1837
+ if (result === null) {
1838
+ return {
1839
+ success: false,
1840
+ error: `Project with ID '${projectId}' not found`,
1841
+ code: "NOT_FOUND"
1842
+ };
1843
+ }
1844
+ return result;
1845
+ } catch (error) {
1846
+ return {
1847
+ success: false,
1848
+ error: error instanceof Error ? error.message : "Failed to batch update dependency view nodes",
1849
+ code: "INTERNAL_ERROR"
1850
+ };
1851
+ }
1852
+ }
1853
+ async removeNode(projectId, viewId, taskId) {
1854
+ try {
1855
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
1856
+ const view = findView(projectData, viewId);
1857
+ if (!view) {
1858
+ return {
1859
+ result: {
1860
+ success: false,
1861
+ error: `Dependency view with ID '${viewId}' not found in project '${projectId}'`,
1862
+ code: "NOT_FOUND"
1863
+ },
1864
+ shouldSave: false
1865
+ };
1866
+ }
1867
+ const nodeIndex = view.nodes.findIndex((node) => node.taskId === taskId);
1868
+ if (nodeIndex === -1) {
1869
+ return {
1870
+ result: {
1871
+ success: false,
1872
+ error: `Task '${taskId}' is not present in dependency view '${viewId}'`,
1873
+ code: "NOT_FOUND"
1874
+ },
1875
+ shouldSave: false
1876
+ };
1877
+ }
1878
+ view.nodes.splice(nodeIndex, 1);
1879
+ const removedEdgeIds = view.edges.filter((edge) => edge.fromTaskId === taskId || edge.toTaskId === taskId).map((edge) => edge.id);
1880
+ view.edges = view.edges.filter((edge) => edge.fromTaskId !== taskId && edge.toTaskId !== taskId);
1881
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1882
+ view.updatedAt = now;
1883
+ view.revision += 1;
1884
+ projectData.project.updatedAt = now;
1885
+ return {
1886
+ result: {
1887
+ success: true,
1888
+ data: {
1889
+ view: cloneView(view),
1890
+ changes: createMutationChanges({
1891
+ removedNodeIds: [taskId],
1892
+ removedEdgeIds
1893
+ })
1894
+ }
1895
+ },
1896
+ shouldSave: true
1897
+ };
1898
+ });
1899
+ if (result === null) {
1900
+ return {
1901
+ success: false,
1902
+ error: `Project with ID '${projectId}' not found`,
1903
+ code: "NOT_FOUND"
1904
+ };
1905
+ }
1906
+ return result;
1907
+ } catch (error) {
1908
+ return {
1909
+ success: false,
1910
+ error: error instanceof Error ? error.message : "Failed to remove task from dependency view",
1911
+ code: "INTERNAL_ERROR"
1912
+ };
1913
+ }
1914
+ }
1915
+ async addEdge(projectId, viewId, data) {
1916
+ try {
1917
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
1918
+ const view = findView(projectData, viewId);
1919
+ if (!view) {
1920
+ return {
1921
+ result: {
1922
+ success: false,
1923
+ error: `Dependency view with ID '${viewId}' not found in project '${projectId}'`,
1924
+ code: "NOT_FOUND"
1925
+ },
1926
+ shouldSave: false
1927
+ };
1928
+ }
1929
+ if (data.fromTaskId === data.toTaskId) {
1930
+ return {
1931
+ result: {
1932
+ success: false,
1933
+ error: "A task cannot depend on itself",
1934
+ code: "VALIDATION_ERROR"
1935
+ },
1936
+ shouldSave: false
1937
+ };
1938
+ }
1939
+ const nodeIds = new Set(view.nodes.map((node) => node.taskId));
1940
+ if (!nodeIds.has(data.fromTaskId) || !nodeIds.has(data.toTaskId)) {
1941
+ return {
1942
+ result: {
1943
+ success: false,
1944
+ error: "Both tasks must be present in the dependency view before connecting them",
1945
+ code: "VALIDATION_ERROR"
1946
+ },
1947
+ shouldSave: false
1948
+ };
1949
+ }
1950
+ const duplicateEdge = view.edges.find(
1951
+ (edge2) => edge2.fromTaskId === data.fromTaskId && edge2.toTaskId === data.toTaskId
1952
+ );
1953
+ if (duplicateEdge) {
1954
+ return {
1955
+ result: {
1956
+ success: false,
1957
+ error: "That dependency already exists in the view",
1958
+ code: "DUPLICATE_ERROR"
1959
+ },
1960
+ shouldSave: false
1961
+ };
1962
+ }
1963
+ const adjacency = buildAdjacency(view);
1964
+ if (canReach(adjacency, data.toTaskId, data.fromTaskId)) {
1965
+ return {
1966
+ result: {
1967
+ success: false,
1968
+ error: "Adding this dependency would create a cycle",
1969
+ code: "VALIDATION_ERROR"
1970
+ },
1971
+ shouldSave: false
1972
+ };
1973
+ }
1974
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1975
+ const edge = {
1976
+ id: generateDependencyViewEdgeId(),
1977
+ fromTaskId: data.fromTaskId,
1978
+ toTaskId: data.toTaskId,
1979
+ kind: "hard",
1980
+ createdAt: now
1981
+ };
1982
+ view.edges.push(edge);
1983
+ view.updatedAt = now;
1984
+ view.revision += 1;
1985
+ projectData.project.updatedAt = now;
1986
+ return {
1987
+ result: {
1988
+ success: true,
1989
+ data: {
1990
+ view: cloneView(view),
1991
+ changes: createMutationChanges({ addedEdgeIds: [edge.id] })
1992
+ }
1993
+ },
1994
+ shouldSave: true
1995
+ };
1996
+ });
1997
+ if (result === null) {
1998
+ return {
1999
+ success: false,
2000
+ error: `Project with ID '${projectId}' not found`,
2001
+ code: "NOT_FOUND"
2002
+ };
2003
+ }
2004
+ return result;
2005
+ } catch (error) {
2006
+ return {
2007
+ success: false,
2008
+ error: error instanceof Error ? error.message : "Failed to add dependency edge",
2009
+ code: "INTERNAL_ERROR"
2010
+ };
2011
+ }
2012
+ }
2013
+ async updateEdge(projectId, viewId, edgeId, data) {
2014
+ try {
2015
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
2016
+ if (Object.keys(data).length === 0) {
2017
+ return {
2018
+ result: {
2019
+ success: false,
2020
+ error: "At least one edge field to update is required",
2021
+ code: "VALIDATION_ERROR"
2022
+ },
2023
+ shouldSave: false
2024
+ };
2025
+ }
2026
+ const view = findView(projectData, viewId);
2027
+ if (!view) {
2028
+ return {
2029
+ result: {
2030
+ success: false,
2031
+ error: `Dependency view with ID '${viewId}' not found in project '${projectId}'`,
2032
+ code: "NOT_FOUND"
2033
+ },
2034
+ shouldSave: false
2035
+ };
2036
+ }
2037
+ const edge = view.edges.find((item) => item.id === edgeId);
2038
+ if (!edge) {
2039
+ return {
2040
+ result: {
2041
+ success: false,
2042
+ error: `Dependency edge with ID '${edgeId}' not found in view '${viewId}'`,
2043
+ code: "NOT_FOUND"
2044
+ },
2045
+ shouldSave: false
2046
+ };
2047
+ }
2048
+ const nextFromTaskId = data.fromTaskId ?? edge.fromTaskId;
2049
+ const nextToTaskId = data.toTaskId ?? edge.toTaskId;
2050
+ if (nextFromTaskId === nextToTaskId) {
2051
+ return {
2052
+ result: {
2053
+ success: false,
2054
+ error: "A task cannot depend on itself",
2055
+ code: "VALIDATION_ERROR"
2056
+ },
2057
+ shouldSave: false
2058
+ };
2059
+ }
2060
+ const nodeIds = new Set(view.nodes.map((node) => node.taskId));
2061
+ if (!nodeIds.has(nextFromTaskId) || !nodeIds.has(nextToTaskId)) {
2062
+ return {
2063
+ result: {
2064
+ success: false,
2065
+ error: "Both tasks must be present in the dependency view before updating an edge",
2066
+ code: "VALIDATION_ERROR"
2067
+ },
2068
+ shouldSave: false
2069
+ };
2070
+ }
2071
+ const duplicateEdge = view.edges.find(
2072
+ (item) => item.id !== edgeId && item.fromTaskId === nextFromTaskId && item.toTaskId === nextToTaskId
2073
+ );
2074
+ if (duplicateEdge) {
2075
+ return {
2076
+ result: {
2077
+ success: false,
2078
+ error: "That dependency already exists in the view",
2079
+ code: "DUPLICATE_ERROR"
2080
+ },
2081
+ shouldSave: false
2082
+ };
2083
+ }
2084
+ const adjacency = buildAdjacency({
2085
+ ...view,
2086
+ edges: view.edges.filter((item) => item.id !== edgeId).map((item) => ({ ...item }))
2087
+ });
2088
+ if (canReach(adjacency, nextToTaskId, nextFromTaskId)) {
2089
+ return {
2090
+ result: {
2091
+ success: false,
2092
+ error: "Updating this dependency would create a cycle",
2093
+ code: "VALIDATION_ERROR"
2094
+ },
2095
+ shouldSave: false
2096
+ };
2097
+ }
2098
+ edge.fromTaskId = nextFromTaskId;
2099
+ edge.toTaskId = nextToTaskId;
2100
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2101
+ view.updatedAt = now;
2102
+ view.revision += 1;
2103
+ projectData.project.updatedAt = now;
2104
+ return {
2105
+ result: {
2106
+ success: true,
2107
+ data: {
2108
+ view: cloneView(view),
2109
+ changes: createMutationChanges({ updatedEdgeIds: [edgeId] })
2110
+ }
2111
+ },
2112
+ shouldSave: true
2113
+ };
2114
+ });
2115
+ if (result === null) {
2116
+ return {
2117
+ success: false,
2118
+ error: `Project with ID '${projectId}' not found`,
2119
+ code: "NOT_FOUND"
2120
+ };
2121
+ }
2122
+ return result;
2123
+ } catch (error) {
2124
+ return {
2125
+ success: false,
2126
+ error: error instanceof Error ? error.message : "Failed to update dependency edge",
2127
+ code: "INTERNAL_ERROR"
2128
+ };
2129
+ }
2130
+ }
2131
+ async removeEdge(projectId, viewId, edgeId) {
2132
+ try {
2133
+ const result = await this.storage.mutateProject(projectId, async (projectData) => {
2134
+ const view = findView(projectData, viewId);
2135
+ if (!view) {
2136
+ return {
2137
+ result: {
2138
+ success: false,
2139
+ error: `Dependency view with ID '${viewId}' not found in project '${projectId}'`,
2140
+ code: "NOT_FOUND"
2141
+ },
2142
+ shouldSave: false
2143
+ };
2144
+ }
2145
+ const edgeIndex = view.edges.findIndex((edge) => edge.id === edgeId);
2146
+ if (edgeIndex === -1) {
2147
+ return {
2148
+ result: {
2149
+ success: false,
2150
+ error: `Dependency edge with ID '${edgeId}' not found in view '${viewId}'`,
2151
+ code: "NOT_FOUND"
2152
+ },
2153
+ shouldSave: false
2154
+ };
2155
+ }
2156
+ view.edges.splice(edgeIndex, 1);
2157
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2158
+ view.updatedAt = now;
2159
+ view.revision += 1;
2160
+ projectData.project.updatedAt = now;
2161
+ return {
2162
+ result: {
2163
+ success: true,
2164
+ data: {
2165
+ view: cloneView(view),
2166
+ changes: createMutationChanges({ removedEdgeIds: [edgeId] })
2167
+ }
2168
+ },
2169
+ shouldSave: true
2170
+ };
2171
+ });
2172
+ if (result === null) {
2173
+ return {
2174
+ success: false,
2175
+ error: `Project with ID '${projectId}' not found`,
2176
+ code: "NOT_FOUND"
2177
+ };
2178
+ }
2179
+ return result;
2180
+ } catch (error) {
2181
+ return {
2182
+ success: false,
2183
+ error: error instanceof Error ? error.message : "Failed to remove dependency edge",
2184
+ code: "INTERNAL_ERROR"
2185
+ };
2186
+ }
2187
+ }
2188
+ async analyze(projectId, viewId) {
2189
+ try {
2190
+ const projectData = await this.storage.readProject(projectId);
1142
2191
  if (!projectData) {
1143
2192
  return {
1144
2193
  success: false,
@@ -1146,90 +2195,28 @@ var TaskService = {
1146
2195
  code: "NOT_FOUND"
1147
2196
  };
1148
2197
  }
1149
- const now = (/* @__PURE__ */ new Date()).toISOString();
1150
- const updatedTasks = [];
1151
- const notFoundIds = [];
1152
- for (const taskId of taskIds) {
1153
- const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
1154
- if (taskIndex === -1) {
1155
- notFoundIds.push(taskId);
1156
- continue;
1157
- }
1158
- const existingTask = projectData.tasks[taskIndex];
1159
- let updatedTags = existingTask.tags ?? [];
1160
- if (data.tags) {
1161
- const invalidTagIds = findInvalidTagIds(projectData, data.tags);
1162
- if (invalidTagIds.length > 0) {
1163
- return {
1164
- success: false,
1165
- error: `Invalid tag IDs for project '${projectId}': ${invalidTagIds.join(", ")}`,
1166
- code: "VALIDATION_ERROR"
1167
- };
1168
- }
1169
- }
1170
- if (data.tags) {
1171
- const existingTags = existingTask.tags ?? [];
1172
- switch (data.tagOperation) {
1173
- case "add":
1174
- updatedTags = [.../* @__PURE__ */ new Set([...existingTags, ...data.tags])];
1175
- break;
1176
- case "remove":
1177
- updatedTags = existingTags.filter((tag) => !data.tags.includes(tag));
1178
- break;
1179
- case "replace":
1180
- default:
1181
- updatedTags = data.tags;
1182
- break;
1183
- }
1184
- }
1185
- const completedAt = calculateCompletedAt(
1186
- existingTask.status,
1187
- data.status,
1188
- existingTask.completedAt,
1189
- now
1190
- );
1191
- const updatedTask = {
1192
- ...existingTask,
1193
- ...data.status && { status: data.status },
1194
- ...data.priority && { priority: data.priority },
1195
- tags: updatedTags,
1196
- updatedAt: now,
1197
- completedAt
1198
- };
1199
- projectData.tasks[taskIndex] = updatedTask;
1200
- updatedTasks.push(updatedTask);
1201
- }
1202
- if (updatedTasks.length === 0) {
2198
+ const view = findView(projectData, viewId);
2199
+ if (!view) {
1203
2200
  return {
1204
2201
  success: false,
1205
- error: "No tasks were found to update",
2202
+ error: `Dependency view with ID '${viewId}' not found in project '${projectId}'`,
1206
2203
  code: "NOT_FOUND"
1207
2204
  };
1208
2205
  }
1209
- projectData.project.updatedAt = now;
1210
- const filePath = storage.getFilePath(projectId);
1211
- await writeJsonFile(filePath, projectData);
1212
2206
  return {
1213
2207
  success: true,
1214
- data: {
1215
- updatedTasks,
1216
- updatedCount: updatedTasks.length,
1217
- notFoundIds: notFoundIds.length > 0 ? notFoundIds : void 0
1218
- }
2208
+ data: analyzeDependencyView(view)
1219
2209
  };
1220
2210
  } catch (error) {
1221
2211
  return {
1222
2212
  success: false,
1223
- error: error instanceof Error ? error.message : "Failed to batch update tasks",
2213
+ error: error instanceof Error ? error.message : "Failed to analyze dependency view",
1224
2214
  code: "INTERNAL_ERROR"
1225
2215
  };
1226
2216
  }
1227
2217
  }
1228
2218
  };
1229
2219
 
1230
- // src/services/types.ts
1231
- init_esm_shims();
1232
-
1233
2220
  // src/tools/task-tools.ts
1234
2221
  var TaskStatusEnum = z2.enum(["todo", "in-progress", "review", "done"]);
1235
2222
  var TaskPriorityEnum = z2.enum(["low", "medium", "high", "critical"]);
@@ -1396,104 +2383,461 @@ var batchUpdateTasksTool = {
1396
2383
  };
1397
2384
  }
1398
2385
  };
1399
-
1400
- // src/tools/tag-tools.ts
1401
- init_esm_shims();
1402
- import { z as z3 } from "zod";
1403
- var tagService = new TagService(storage);
1404
- var createTagTool = {
1405
- name: "create_tag",
1406
- description: "Create a new tag in a project. If color is omitted, it is generated deterministically from tag name.",
1407
- inputSchema: z3.object({
1408
- projectId: z3.string().min(1, "Project ID is required"),
1409
- name: z3.string().min(1, "Tag name is required"),
1410
- color: z3.string().regex(/^#[0-9A-Fa-f]{6}$/, "Color must be a valid hex code (e.g., #FF5733)").optional(),
1411
- description: z3.string().default("")
2386
+
2387
+ // src/tools/tag-tools.ts
2388
+ import { z as z3 } from "zod";
2389
+ var tagService = new TagService(storage);
2390
+ var createTagTool = {
2391
+ name: "create_tag",
2392
+ description: "Create a new tag in a project. If color is omitted, it is generated deterministically from tag name.",
2393
+ inputSchema: z3.object({
2394
+ projectId: z3.string().min(1, "Project ID is required"),
2395
+ name: z3.string().min(1, "Tag name is required"),
2396
+ color: z3.string().regex(/^#[0-9A-Fa-f]{6}$/, "Color must be a valid hex code (e.g., #FF5733)").optional(),
2397
+ description: z3.string().default("")
2398
+ }),
2399
+ async execute(input) {
2400
+ const { projectId, ...data } = input;
2401
+ const result = await tagService.create(projectId, data);
2402
+ if (!result.success) {
2403
+ return { success: false, error: result.error };
2404
+ }
2405
+ return { success: true, data: result.data };
2406
+ }
2407
+ };
2408
+ var listTagsTool = {
2409
+ name: "list_tags",
2410
+ description: "List all tags in a project",
2411
+ inputSchema: z3.object({
2412
+ projectId: z3.string().min(1, "Project ID is required")
2413
+ }),
2414
+ async execute(input) {
2415
+ const result = await tagService.list(input.projectId);
2416
+ if (!result.success) {
2417
+ return { success: false, error: result.error };
2418
+ }
2419
+ return { success: true, data: result.data };
2420
+ }
2421
+ };
2422
+ var updateTagTool = {
2423
+ name: "update_tag",
2424
+ description: "Update an existing tag",
2425
+ inputSchema: z3.object({
2426
+ projectId: z3.string().min(1, "Project ID is required"),
2427
+ tagId: z3.string().min(1, "Tag ID is required"),
2428
+ name: z3.string().min(1).optional(),
2429
+ color: z3.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
2430
+ description: z3.string().optional()
2431
+ }),
2432
+ async execute(input) {
2433
+ const { projectId, tagId, ...data } = input;
2434
+ const result = await tagService.update(projectId, tagId, data);
2435
+ if (!result.success) {
2436
+ return { success: false, error: result.error };
2437
+ }
2438
+ return { success: true, data: result.data };
2439
+ }
2440
+ };
2441
+ var deleteTagTool = {
2442
+ name: "delete_tag",
2443
+ description: "Delete a tag by project ID and tag ID",
2444
+ inputSchema: z3.object({
2445
+ projectId: z3.string().min(1, "Project ID is required"),
2446
+ tagId: z3.string().min(1, "Tag ID is required")
2447
+ }),
2448
+ async execute(input) {
2449
+ const result = await tagService.delete(input.projectId, input.tagId);
2450
+ if (!result.success) {
2451
+ return { success: false, error: result.error };
2452
+ }
2453
+ return { success: true, data: result.data };
2454
+ }
2455
+ };
2456
+ var getTasksByTagTool = {
2457
+ name: "get_tasks_by_tag",
2458
+ description: "Get all tasks that have a specific tag",
2459
+ inputSchema: z3.object({
2460
+ projectId: z3.string().min(1, "Project ID is required"),
2461
+ tagName: z3.string().min(1, "Tag name is required")
2462
+ }),
2463
+ async execute(input) {
2464
+ const result = await tagService.getTasksByTag(input.projectId, input.tagName);
2465
+ if (!result.success) {
2466
+ return { success: false, error: result.error };
2467
+ }
2468
+ return { success: true, data: result.data };
2469
+ }
2470
+ };
2471
+
2472
+ // src/tools/dependency-view-tools.ts
2473
+ import { z as z4 } from "zod";
2474
+ var dependencyViewService = new DependencyViewService(storage);
2475
+ function toDependencyViewSummary(view) {
2476
+ return {
2477
+ id: view.id,
2478
+ projectId: view.projectId,
2479
+ name: view.name,
2480
+ description: view.description,
2481
+ dimension: view.dimension,
2482
+ revision: view.revision,
2483
+ nodeCount: view.nodes.length,
2484
+ edgeCount: view.edges.length
2485
+ };
2486
+ }
2487
+ function toTaskSummary3(task) {
2488
+ return {
2489
+ id: task.id,
2490
+ title: task.title,
2491
+ status: task.status,
2492
+ priority: task.priority,
2493
+ dueDate: task.dueDate,
2494
+ assignee: task.assignee,
2495
+ tags: [...task.tags]
2496
+ };
2497
+ }
2498
+ function toAgentDependencyView(view, tasks) {
2499
+ const tasksById = new Map(tasks.map((task) => [task.id, task]));
2500
+ return {
2501
+ ...toDependencyViewSummary(view),
2502
+ nodes: view.nodes.flatMap((node) => {
2503
+ const task = tasksById.get(node.taskId);
2504
+ if (!task) {
2505
+ return [];
2506
+ }
2507
+ return [{
2508
+ taskId: node.taskId,
2509
+ task: toTaskSummary3(task)
2510
+ }];
2511
+ }),
2512
+ edges: view.edges.map((edge) => ({
2513
+ id: edge.id,
2514
+ fromTaskId: edge.fromTaskId,
2515
+ toTaskId: edge.toTaskId
2516
+ }))
2517
+ };
2518
+ }
2519
+ function toDependencyViewMutationResult(mutation) {
2520
+ return {
2521
+ ...toDependencyViewSummary(mutation.view),
2522
+ changes: mutation.changes
2523
+ };
2524
+ }
2525
+ var NullableString = z4.string().nullable();
2526
+ var createDependencyViewTool = {
2527
+ name: "create_dependency_view",
2528
+ description: "Create a dependency planning view inside a project. Returns summary by default; set verbose=true for full data.",
2529
+ inputSchema: z4.object({
2530
+ projectId: z4.string().min(1, "Project ID is required"),
2531
+ name: z4.string().min(1, "View name is required"),
2532
+ description: z4.string().default(""),
2533
+ dimension: NullableString.optional(),
2534
+ verbose: z4.boolean().optional()
2535
+ }),
2536
+ async execute(input) {
2537
+ const result = await dependencyViewService.create(input.projectId, input);
2538
+ if (!result.success) {
2539
+ return result;
2540
+ }
2541
+ return {
2542
+ success: true,
2543
+ data: input.verbose ? result.data : toDependencyViewSummary(result.data)
2544
+ };
2545
+ }
2546
+ };
2547
+ var listDependencyViewsTool = {
2548
+ name: "list_dependency_views",
2549
+ description: "List dependency planning views in a project. Returns summaries by default; set includeTasks=true for Agent-friendly task snapshots or verbose=true for raw node and edge data.",
2550
+ inputSchema: z4.object({
2551
+ projectId: z4.string().min(1, "Project ID is required"),
2552
+ includeTasks: z4.boolean().optional(),
2553
+ verbose: z4.boolean().optional()
2554
+ }),
2555
+ async execute(input) {
2556
+ const result = await dependencyViewService.list(input.projectId);
2557
+ if (!result.success) {
2558
+ return result;
2559
+ }
2560
+ if (input.verbose) {
2561
+ return {
2562
+ success: true,
2563
+ data: result.data
2564
+ };
2565
+ }
2566
+ if (input.includeTasks) {
2567
+ const projectData = await storage.readProject(input.projectId);
2568
+ if (!projectData) {
2569
+ return {
2570
+ success: false,
2571
+ error: `Project with ID '${input.projectId}' not found`,
2572
+ code: "NOT_FOUND"
2573
+ };
2574
+ }
2575
+ return {
2576
+ success: true,
2577
+ data: result.data.map((view) => toAgentDependencyView(view, projectData.tasks))
2578
+ };
2579
+ }
2580
+ return {
2581
+ success: true,
2582
+ data: result.data.map(toDependencyViewSummary)
2583
+ };
2584
+ }
2585
+ };
2586
+ var getDependencyViewTool = {
2587
+ name: "get_dependency_view",
2588
+ description: "Get a dependency planning view by project ID and view ID. Returns Agent-friendly task snapshots by default; set verbose=true for raw node and edge data.",
2589
+ inputSchema: z4.object({
2590
+ projectId: z4.string().min(1, "Project ID is required"),
2591
+ viewId: z4.string().min(1, "View ID is required"),
2592
+ verbose: z4.boolean().optional()
2593
+ }),
2594
+ async execute(input) {
2595
+ const result = await dependencyViewService.get(input.projectId, input.viewId);
2596
+ if (!result.success) {
2597
+ return result;
2598
+ }
2599
+ if (input.verbose) {
2600
+ return {
2601
+ success: true,
2602
+ data: result.data
2603
+ };
2604
+ }
2605
+ const projectData = await storage.readProject(input.projectId);
2606
+ if (!projectData) {
2607
+ return {
2608
+ success: false,
2609
+ error: `Project with ID '${input.projectId}' not found`,
2610
+ code: "NOT_FOUND"
2611
+ };
2612
+ }
2613
+ return {
2614
+ success: true,
2615
+ data: toAgentDependencyView(result.data, projectData.tasks)
2616
+ };
2617
+ }
2618
+ };
2619
+ var updateDependencyViewTool = {
2620
+ name: "update_dependency_view",
2621
+ description: "Update dependency planning view metadata. Returns summary by default; set verbose=true for full data.",
2622
+ inputSchema: z4.object({
2623
+ projectId: z4.string().min(1, "Project ID is required"),
2624
+ viewId: z4.string().min(1, "View ID is required"),
2625
+ name: z4.string().min(1).optional(),
2626
+ description: z4.string().optional(),
2627
+ dimension: NullableString.optional(),
2628
+ verbose: z4.boolean().optional()
2629
+ }),
2630
+ async execute(input) {
2631
+ const { projectId, viewId, verbose, ...updateData } = input;
2632
+ const result = await dependencyViewService.update(projectId, viewId, updateData);
2633
+ if (!result.success) {
2634
+ return result;
2635
+ }
2636
+ return {
2637
+ success: true,
2638
+ data: verbose ? result.data : toDependencyViewSummary(result.data)
2639
+ };
2640
+ }
2641
+ };
2642
+ var deleteDependencyViewTool = {
2643
+ name: "delete_dependency_view",
2644
+ description: "Delete a dependency planning view by project ID and view ID",
2645
+ inputSchema: z4.object({
2646
+ projectId: z4.string().min(1, "Project ID is required"),
2647
+ viewId: z4.string().min(1, "View ID is required")
2648
+ }),
2649
+ async execute(input) {
2650
+ const result = await dependencyViewService.delete(input.projectId, input.viewId);
2651
+ if (!result.success) {
2652
+ return result;
2653
+ }
2654
+ return {
2655
+ success: true,
2656
+ data: { deleted: true }
2657
+ };
2658
+ }
2659
+ };
2660
+ var addTaskToDependencyViewTool = {
2661
+ name: "add_task_to_dependency_view",
2662
+ description: "Add an existing task into a dependency planning view. Returns summary plus changed ids by default; set verbose=true for full data.",
2663
+ inputSchema: z4.object({
2664
+ projectId: z4.string().min(1, "Project ID is required"),
2665
+ viewId: z4.string().min(1, "View ID is required"),
2666
+ taskId: z4.string().min(1, "Task ID is required"),
2667
+ x: z4.number().optional(),
2668
+ y: z4.number().optional(),
2669
+ collapsed: z4.boolean().optional(),
2670
+ note: NullableString.optional(),
2671
+ verbose: z4.boolean().optional()
2672
+ }),
2673
+ async execute(input) {
2674
+ const { projectId, viewId, verbose, ...nodeData } = input;
2675
+ const result = await dependencyViewService.addNode(projectId, viewId, nodeData);
2676
+ if (!result.success) {
2677
+ return result;
2678
+ }
2679
+ return {
2680
+ success: true,
2681
+ data: verbose ? result.data.view : toDependencyViewMutationResult(result.data)
2682
+ };
2683
+ }
2684
+ };
2685
+ var updateDependencyViewNodeTool = {
2686
+ name: "update_dependency_view_node",
2687
+ description: "Update a task node layout or note inside a dependency planning view. Returns summary plus changed ids by default; set verbose=true for full data.",
2688
+ inputSchema: z4.object({
2689
+ projectId: z4.string().min(1, "Project ID is required"),
2690
+ viewId: z4.string().min(1, "View ID is required"),
2691
+ taskId: z4.string().min(1, "Task ID is required"),
2692
+ x: z4.number().optional(),
2693
+ y: z4.number().optional(),
2694
+ collapsed: z4.boolean().optional(),
2695
+ note: NullableString.optional(),
2696
+ verbose: z4.boolean().optional()
2697
+ }),
2698
+ async execute(input) {
2699
+ const { projectId, viewId, taskId, verbose, ...nodeData } = input;
2700
+ const result = await dependencyViewService.updateNode(projectId, viewId, taskId, nodeData);
2701
+ if (!result.success) {
2702
+ return result;
2703
+ }
2704
+ return {
2705
+ success: true,
2706
+ data: verbose ? result.data.view : toDependencyViewMutationResult(result.data)
2707
+ };
2708
+ }
2709
+ };
2710
+ var batchUpdateDependencyViewNodesTool = {
2711
+ name: "batch_update_dependency_view_nodes",
2712
+ description: "Update multiple task node layouts or notes inside a dependency planning view. Returns summary plus changed ids by default; set verbose=true for full data.",
2713
+ inputSchema: z4.object({
2714
+ projectId: z4.string().min(1, "Project ID is required"),
2715
+ viewId: z4.string().min(1, "View ID is required"),
2716
+ nodes: z4.array(z4.object({
2717
+ taskId: z4.string().min(1, "Task ID is required"),
2718
+ x: z4.number().optional(),
2719
+ y: z4.number().optional(),
2720
+ collapsed: z4.boolean().optional(),
2721
+ note: NullableString.optional()
2722
+ })).min(1, "At least one node update is required"),
2723
+ verbose: z4.boolean().optional()
2724
+ }),
2725
+ async execute(input) {
2726
+ const { projectId, viewId, verbose, ...nodeData } = input;
2727
+ const result = await dependencyViewService.batchUpdateNodes(projectId, viewId, nodeData);
2728
+ if (!result.success) {
2729
+ return result;
2730
+ }
2731
+ return {
2732
+ success: true,
2733
+ data: verbose ? result.data.view : toDependencyViewMutationResult(result.data)
2734
+ };
2735
+ }
2736
+ };
2737
+ var removeTaskFromDependencyViewTool = {
2738
+ name: "remove_task_from_dependency_view",
2739
+ description: "Remove a task node from a dependency planning view. Connected edges are removed too. Returns summary plus changed ids by default; set verbose=true for full data.",
2740
+ inputSchema: z4.object({
2741
+ projectId: z4.string().min(1, "Project ID is required"),
2742
+ viewId: z4.string().min(1, "View ID is required"),
2743
+ taskId: z4.string().min(1, "Task ID is required"),
2744
+ verbose: z4.boolean().optional()
1412
2745
  }),
1413
2746
  async execute(input) {
1414
- const { projectId, ...data } = input;
1415
- const result = await tagService.create(projectId, data);
2747
+ const result = await dependencyViewService.removeNode(input.projectId, input.viewId, input.taskId);
1416
2748
  if (!result.success) {
1417
- return { success: false, error: result.error };
2749
+ return result;
1418
2750
  }
1419
- return { success: true, data: result.data };
2751
+ return {
2752
+ success: true,
2753
+ data: input.verbose ? result.data.view : toDependencyViewMutationResult(result.data)
2754
+ };
1420
2755
  }
1421
2756
  };
1422
- var listTagsTool = {
1423
- name: "list_tags",
1424
- description: "List all tags in a project",
1425
- inputSchema: z3.object({
1426
- projectId: z3.string().min(1, "Project ID is required")
2757
+ var addDependencyViewEdgeTool = {
2758
+ name: "add_dependency_view_edge",
2759
+ description: "Create a dependency edge between two tasks already present in a dependency planning view. Returns summary plus changed ids by default; set verbose=true for full data.",
2760
+ inputSchema: z4.object({
2761
+ projectId: z4.string().min(1, "Project ID is required"),
2762
+ viewId: z4.string().min(1, "View ID is required"),
2763
+ fromTaskId: z4.string().min(1, "Source task ID is required"),
2764
+ toTaskId: z4.string().min(1, "Target task ID is required"),
2765
+ verbose: z4.boolean().optional()
1427
2766
  }),
1428
2767
  async execute(input) {
1429
- const result = await tagService.list(input.projectId);
2768
+ const { projectId, viewId, verbose, ...edgeData } = input;
2769
+ const result = await dependencyViewService.addEdge(projectId, viewId, edgeData);
1430
2770
  if (!result.success) {
1431
- return { success: false, error: result.error };
2771
+ return result;
1432
2772
  }
1433
- return { success: true, data: result.data };
2773
+ return {
2774
+ success: true,
2775
+ data: verbose ? result.data.view : toDependencyViewMutationResult(result.data)
2776
+ };
1434
2777
  }
1435
2778
  };
1436
- var updateTagTool = {
1437
- name: "update_tag",
1438
- description: "Update an existing tag",
1439
- inputSchema: z3.object({
1440
- projectId: z3.string().min(1, "Project ID is required"),
1441
- tagId: z3.string().min(1, "Tag ID is required"),
1442
- name: z3.string().min(1).optional(),
1443
- color: z3.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
1444
- description: z3.string().optional()
2779
+ var removeDependencyViewEdgeTool = {
2780
+ name: "remove_dependency_view_edge",
2781
+ description: "Remove a dependency edge from a dependency planning view. Returns summary plus changed ids by default; set verbose=true for full data.",
2782
+ inputSchema: z4.object({
2783
+ projectId: z4.string().min(1, "Project ID is required"),
2784
+ viewId: z4.string().min(1, "View ID is required"),
2785
+ edgeId: z4.string().min(1, "Edge ID is required"),
2786
+ verbose: z4.boolean().optional()
1445
2787
  }),
1446
2788
  async execute(input) {
1447
- const { projectId, tagId, ...data } = input;
1448
- const result = await tagService.update(projectId, tagId, data);
2789
+ const result = await dependencyViewService.removeEdge(input.projectId, input.viewId, input.edgeId);
1449
2790
  if (!result.success) {
1450
- return { success: false, error: result.error };
2791
+ return result;
1451
2792
  }
1452
- return { success: true, data: result.data };
2793
+ return {
2794
+ success: true,
2795
+ data: input.verbose ? result.data.view : toDependencyViewMutationResult(result.data)
2796
+ };
1453
2797
  }
1454
2798
  };
1455
- var deleteTagTool = {
1456
- name: "delete_tag",
1457
- description: "Delete a tag by project ID and tag ID",
1458
- inputSchema: z3.object({
1459
- projectId: z3.string().min(1, "Project ID is required"),
1460
- tagId: z3.string().min(1, "Tag ID is required")
2799
+ var updateDependencyViewEdgeTool = {
2800
+ name: "update_dependency_view_edge",
2801
+ description: "Update the direction of a dependency edge inside a dependency planning view. Returns summary plus changed ids by default; set verbose=true for full data.",
2802
+ inputSchema: z4.object({
2803
+ projectId: z4.string().min(1, "Project ID is required"),
2804
+ viewId: z4.string().min(1, "View ID is required"),
2805
+ edgeId: z4.string().min(1, "Edge ID is required"),
2806
+ fromTaskId: z4.string().min(1).optional(),
2807
+ toTaskId: z4.string().min(1).optional(),
2808
+ verbose: z4.boolean().optional()
1461
2809
  }),
1462
2810
  async execute(input) {
1463
- const result = await tagService.delete(input.projectId, input.tagId);
2811
+ const { projectId, viewId, edgeId, verbose, ...edgeData } = input;
2812
+ const result = await dependencyViewService.updateEdge(projectId, viewId, edgeId, edgeData);
1464
2813
  if (!result.success) {
1465
- return { success: false, error: result.error };
2814
+ return result;
1466
2815
  }
1467
- return { success: true, data: result.data };
2816
+ return {
2817
+ success: true,
2818
+ data: verbose ? result.data.view : toDependencyViewMutationResult(result.data)
2819
+ };
1468
2820
  }
1469
2821
  };
1470
- var getTasksByTagTool = {
1471
- name: "get_tasks_by_tag",
1472
- description: "Get all tasks that have a specific tag",
1473
- inputSchema: z3.object({
1474
- projectId: z3.string().min(1, "Project ID is required"),
1475
- tagName: z3.string().min(1, "Tag name is required")
2822
+ var analyzeDependencyViewTool = {
2823
+ name: "analyze_dependency_view",
2824
+ description: "Analyze a dependency planning view and return topological layers, ready tasks, blocked tasks, roots, leaves, and isolated tasks.",
2825
+ inputSchema: z4.object({
2826
+ projectId: z4.string().min(1, "Project ID is required"),
2827
+ viewId: z4.string().min(1, "View ID is required")
1476
2828
  }),
1477
2829
  async execute(input) {
1478
- const result = await tagService.getTasksByTag(input.projectId, input.tagName);
1479
- if (!result.success) {
1480
- return { success: false, error: result.error };
1481
- }
1482
- return { success: true, data: result.data };
2830
+ return await dependencyViewService.analyze(input.projectId, input.viewId);
1483
2831
  }
1484
2832
  };
1485
2833
 
1486
- // src/tools/web-tools.ts
1487
- init_esm_shims();
1488
-
1489
2834
  // src/web/server.ts
1490
- init_esm_shims();
1491
2835
  import express from "express";
1492
2836
  import * as path4 from "path";
1493
- import { fileURLToPath as fileURLToPath2 } from "url";
2837
+ import { fileURLToPath } from "url";
1494
2838
  import { createRequire } from "module";
1495
2839
  import { existsSync } from "fs";
1496
- var __filename2 = fileURLToPath2(import.meta.url);
2840
+ var __filename2 = fileURLToPath(import.meta.url);
1497
2841
  var __dirname2 = path4.dirname(__filename2);
1498
2842
  function resolveAppPath() {
1499
2843
  const candidates = [];
@@ -1525,10 +2869,61 @@ Ensure the package is installed correctly and web assets exist.`
1525
2869
  );
1526
2870
  }
1527
2871
  var tagService2 = new TagService(storage);
2872
+ var dependencyViewService2 = new DependencyViewService(storage);
2873
+ function unwrapDependencyViewMutation(data) {
2874
+ if (typeof data !== "object" || data === null || !("view" in data)) {
2875
+ return data;
2876
+ }
2877
+ return data.view;
2878
+ }
2879
+ function normalizeRepositoryUrl(repository) {
2880
+ if (!repository) {
2881
+ return null;
2882
+ }
2883
+ if (repository.startsWith("git@github.com:")) {
2884
+ return `https://github.com/${repository.slice("git@github.com:".length).replace(/\.git$/, "")}`;
2885
+ }
2886
+ return repository.replace(/^git\+/, "").replace(/\.git$/, "");
2887
+ }
2888
+ function resolveAppMetadata() {
2889
+ const fallback = {
2890
+ version: "0.0.0",
2891
+ repositoryUrl: "https://github.com/shiquda/roadmap-skill"
2892
+ };
2893
+ const candidates = [];
2894
+ try {
2895
+ const require2 = createRequire(import.meta.url);
2896
+ try {
2897
+ const pkgRoot = path4.dirname(require2.resolve("../../package.json"));
2898
+ candidates.push(path4.join(pkgRoot, "package.json"));
2899
+ } catch {
2900
+ }
2901
+ candidates.push(path4.join(process.cwd(), "package.json"));
2902
+ candidates.push(path4.join(__dirname2, "../../package.json"));
2903
+ candidates.push(path4.join(__dirname2, "../package.json"));
2904
+ for (const candidate of candidates) {
2905
+ if (!existsSync(candidate)) {
2906
+ continue;
2907
+ }
2908
+ const pkg = require2(candidate);
2909
+ const repository = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url;
2910
+ return {
2911
+ version: pkg.version ?? fallback.version,
2912
+ repositoryUrl: normalizeRepositoryUrl(repository) ?? pkg.homepage ?? fallback.repositoryUrl
2913
+ };
2914
+ }
2915
+ } catch {
2916
+ }
2917
+ return fallback;
2918
+ }
2919
+ var appMetadata = resolveAppMetadata();
1528
2920
  function createServer(port = 7860) {
1529
2921
  return new Promise((resolve, reject) => {
1530
2922
  const app = express();
1531
2923
  app.use(express.json());
2924
+ app.get("/api/meta", (_req, res) => {
2925
+ res.json(appMetadata);
2926
+ });
1532
2927
  app.get("/api/projects", async (_req, res) => {
1533
2928
  try {
1534
2929
  const projects = await storage.listProjects();
@@ -1549,6 +2944,189 @@ function createServer(port = 7860) {
1549
2944
  res.status(500).json({ error: error.message });
1550
2945
  }
1551
2946
  });
2947
+ app.get("/api/projects/:projectId/dependency-views", async (req, res) => {
2948
+ try {
2949
+ const result = await dependencyViewService2.list(req.params.projectId);
2950
+ if (!result.success) {
2951
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
2952
+ res.status(statusCode).json({ error: result.error });
2953
+ return;
2954
+ }
2955
+ res.json({ success: true, data: unwrapDependencyViewMutation(result.data) });
2956
+ } catch (error) {
2957
+ res.status(500).json({ error: error.message });
2958
+ }
2959
+ });
2960
+ app.post("/api/projects/:projectId/dependency-views", async (req, res) => {
2961
+ try {
2962
+ const result = await dependencyViewService2.create(req.params.projectId, req.body);
2963
+ if (!result.success) {
2964
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
2965
+ res.status(statusCode).json({ error: result.error });
2966
+ return;
2967
+ }
2968
+ res.json({ success: true, data: unwrapDependencyViewMutation(result.data) });
2969
+ } catch (error) {
2970
+ res.status(500).json({ error: error.message });
2971
+ }
2972
+ });
2973
+ app.get("/api/projects/:projectId/dependency-views/:viewId", async (req, res) => {
2974
+ try {
2975
+ const result = await dependencyViewService2.get(req.params.projectId, req.params.viewId);
2976
+ if (!result.success) {
2977
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
2978
+ res.status(statusCode).json({ error: result.error });
2979
+ return;
2980
+ }
2981
+ res.json({ success: true, data: unwrapDependencyViewMutation(result.data) });
2982
+ } catch (error) {
2983
+ res.status(500).json({ error: error.message });
2984
+ }
2985
+ });
2986
+ app.put("/api/projects/:projectId/dependency-views/:viewId", async (req, res) => {
2987
+ try {
2988
+ const result = await dependencyViewService2.update(req.params.projectId, req.params.viewId, req.body);
2989
+ if (!result.success) {
2990
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
2991
+ res.status(statusCode).json({ error: result.error });
2992
+ return;
2993
+ }
2994
+ res.json({ success: true, data: unwrapDependencyViewMutation(result.data) });
2995
+ } catch (error) {
2996
+ res.status(500).json({ error: error.message });
2997
+ }
2998
+ });
2999
+ app.delete("/api/projects/:projectId/dependency-views/:viewId", async (req, res) => {
3000
+ try {
3001
+ const result = await dependencyViewService2.delete(req.params.projectId, req.params.viewId);
3002
+ if (!result.success) {
3003
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
3004
+ res.status(statusCode).json({ error: result.error });
3005
+ return;
3006
+ }
3007
+ res.json({ success: true });
3008
+ } catch (error) {
3009
+ res.status(500).json({ error: error.message });
3010
+ }
3011
+ });
3012
+ app.post("/api/projects/:projectId/dependency-views/:viewId/nodes", async (req, res) => {
3013
+ try {
3014
+ const result = await dependencyViewService2.addNode(req.params.projectId, req.params.viewId, req.body);
3015
+ if (!result.success) {
3016
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
3017
+ res.status(statusCode).json({ error: result.error });
3018
+ return;
3019
+ }
3020
+ res.json({ success: true, data: unwrapDependencyViewMutation(result.data) });
3021
+ } catch (error) {
3022
+ res.status(500).json({ error: error.message });
3023
+ }
3024
+ });
3025
+ app.put("/api/projects/:projectId/dependency-views/:viewId/nodes/:taskId", async (req, res) => {
3026
+ try {
3027
+ const result = await dependencyViewService2.updateNode(
3028
+ req.params.projectId,
3029
+ req.params.viewId,
3030
+ req.params.taskId,
3031
+ req.body
3032
+ );
3033
+ if (!result.success) {
3034
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
3035
+ res.status(statusCode).json({ error: result.error });
3036
+ return;
3037
+ }
3038
+ res.json({ success: true, data: unwrapDependencyViewMutation(result.data) });
3039
+ } catch (error) {
3040
+ res.status(500).json({ error: error.message });
3041
+ }
3042
+ });
3043
+ app.put("/api/projects/:projectId/dependency-views/:viewId/nodes", async (req, res) => {
3044
+ try {
3045
+ const result = await dependencyViewService2.batchUpdateNodes(
3046
+ req.params.projectId,
3047
+ req.params.viewId,
3048
+ req.body
3049
+ );
3050
+ if (!result.success) {
3051
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
3052
+ res.status(statusCode).json({ error: result.error });
3053
+ return;
3054
+ }
3055
+ res.json({ success: true, data: unwrapDependencyViewMutation(result.data) });
3056
+ } catch (error) {
3057
+ res.status(500).json({ error: error.message });
3058
+ }
3059
+ });
3060
+ app.delete("/api/projects/:projectId/dependency-views/:viewId/nodes/:taskId", async (req, res) => {
3061
+ try {
3062
+ const result = await dependencyViewService2.removeNode(req.params.projectId, req.params.viewId, req.params.taskId);
3063
+ if (!result.success) {
3064
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
3065
+ res.status(statusCode).json({ error: result.error });
3066
+ return;
3067
+ }
3068
+ res.json({ success: true, data: unwrapDependencyViewMutation(result.data) });
3069
+ } catch (error) {
3070
+ res.status(500).json({ error: error.message });
3071
+ }
3072
+ });
3073
+ app.post("/api/projects/:projectId/dependency-views/:viewId/edges", async (req, res) => {
3074
+ try {
3075
+ const result = await dependencyViewService2.addEdge(req.params.projectId, req.params.viewId, req.body);
3076
+ if (!result.success) {
3077
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
3078
+ res.status(statusCode).json({ error: result.error });
3079
+ return;
3080
+ }
3081
+ res.json({ success: true, data: unwrapDependencyViewMutation(result.data) });
3082
+ } catch (error) {
3083
+ res.status(500).json({ error: error.message });
3084
+ }
3085
+ });
3086
+ app.put("/api/projects/:projectId/dependency-views/:viewId/edges/:edgeId", async (req, res) => {
3087
+ try {
3088
+ const result = await dependencyViewService2.updateEdge(
3089
+ req.params.projectId,
3090
+ req.params.viewId,
3091
+ req.params.edgeId,
3092
+ req.body
3093
+ );
3094
+ if (!result.success) {
3095
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
3096
+ res.status(statusCode).json({ error: result.error });
3097
+ return;
3098
+ }
3099
+ res.json({ success: true, data: unwrapDependencyViewMutation(result.data) });
3100
+ } catch (error) {
3101
+ res.status(500).json({ error: error.message });
3102
+ }
3103
+ });
3104
+ app.delete("/api/projects/:projectId/dependency-views/:viewId/edges/:edgeId", async (req, res) => {
3105
+ try {
3106
+ const result = await dependencyViewService2.removeEdge(req.params.projectId, req.params.viewId, req.params.edgeId);
3107
+ if (!result.success) {
3108
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
3109
+ res.status(statusCode).json({ error: result.error });
3110
+ return;
3111
+ }
3112
+ res.json({ success: true, data: unwrapDependencyViewMutation(result.data) });
3113
+ } catch (error) {
3114
+ res.status(500).json({ error: error.message });
3115
+ }
3116
+ });
3117
+ app.get("/api/projects/:projectId/dependency-views/:viewId/analyze", async (req, res) => {
3118
+ try {
3119
+ const result = await dependencyViewService2.analyze(req.params.projectId, req.params.viewId);
3120
+ if (!result.success) {
3121
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
3122
+ res.status(statusCode).json({ error: result.error });
3123
+ return;
3124
+ }
3125
+ res.json({ success: true, data: result.data });
3126
+ } catch (error) {
3127
+ res.status(500).json({ error: error.message });
3128
+ }
3129
+ });
1552
3130
  app.get("/api/tasks", async (req, res) => {
1553
3131
  try {
1554
3132
  const filters = { ...req.query };
@@ -1837,8 +3415,7 @@ var closeWebInterfaceTool = {
1837
3415
  };
1838
3416
 
1839
3417
  // src/tools/template-tools.ts
1840
- init_esm_shims();
1841
- import { z as z4 } from "zod";
3418
+ import { z as z5 } from "zod";
1842
3419
  import * as path5 from "path";
1843
3420
  import * as fs2 from "fs/promises";
1844
3421
  var TEMPLATES_DIR = path5.join(process.cwd(), "templates");
@@ -1866,7 +3443,7 @@ function generateId(prefix) {
1866
3443
  var listTemplatesTool = {
1867
3444
  name: "list_templates",
1868
3445
  description: "List all available project templates",
1869
- inputSchema: z4.object({}),
3446
+ inputSchema: z5.object({}),
1870
3447
  async execute() {
1871
3448
  try {
1872
3449
  const templateFiles = await getTemplateFiles();
@@ -1899,8 +3476,8 @@ var listTemplatesTool = {
1899
3476
  var getTemplateTool = {
1900
3477
  name: "get_template",
1901
3478
  description: "Get detailed information about a specific template",
1902
- inputSchema: z4.object({
1903
- templateName: z4.string().min(1, "Template name is required")
3479
+ inputSchema: z5.object({
3480
+ templateName: z5.string().min(1, "Template name is required")
1904
3481
  }),
1905
3482
  async execute(input) {
1906
3483
  try {
@@ -1938,12 +3515,12 @@ var getTemplateTool = {
1938
3515
  var applyTemplateTool = {
1939
3516
  name: "apply_template",
1940
3517
  description: "Create a new project from a template",
1941
- inputSchema: z4.object({
1942
- templateName: z4.string().min(1, "Template name is required"),
1943
- projectName: z4.string().min(1, "Project name is required"),
1944
- description: z4.string().default(""),
1945
- startDate: z4.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").optional(),
1946
- targetDate: z4.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").optional()
3518
+ inputSchema: z5.object({
3519
+ templateName: z5.string().min(1, "Template name is required"),
3520
+ projectName: z5.string().min(1, "Project name is required"),
3521
+ description: z5.string().default(""),
3522
+ startDate: z5.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").optional(),
3523
+ targetDate: z5.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").optional()
1947
3524
  }),
1948
3525
  async execute(input) {
1949
3526
  try {
@@ -1996,16 +3573,25 @@ var applyTemplateTool = {
1996
3573
  };
1997
3574
  tasks.push(task);
1998
3575
  }
1999
- projectData.tags = tags;
2000
- projectData.tasks = tasks;
2001
- projectData.project.updatedAt = now;
2002
- const filePath = storage.getFilePath(projectId);
2003
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
2004
- await writeJsonFile2(filePath, projectData);
3576
+ const updatedProjectData = await storage.mutateProject(projectId, async (storedProjectData) => {
3577
+ storedProjectData.tags = tags;
3578
+ storedProjectData.tasks = tasks;
3579
+ storedProjectData.project.updatedAt = now;
3580
+ return {
3581
+ result: storedProjectData,
3582
+ shouldSave: true
3583
+ };
3584
+ });
3585
+ if (!updatedProjectData) {
3586
+ return {
3587
+ success: false,
3588
+ error: `Project '${projectId}' not found after creation`
3589
+ };
3590
+ }
2005
3591
  return {
2006
3592
  success: true,
2007
3593
  data: {
2008
- project: projectData.project,
3594
+ project: updatedProjectData.project,
2009
3595
  taskCount: tasks.length,
2010
3596
  tagCount: tags.length,
2011
3597
  tasksCreated: tasks.map((t) => ({
@@ -2029,11 +3615,7 @@ var applyTemplateTool = {
2029
3615
  }
2030
3616
  };
2031
3617
 
2032
- // src/resources/index.ts
2033
- init_esm_shims();
2034
-
2035
3618
  // src/resources/project-resources.ts
2036
- init_esm_shims();
2037
3619
  var projectResources = [
2038
3620
  {
2039
3621
  uri: "roadmap://projects",
@@ -2238,11 +3820,7 @@ function getAllResources() {
2238
3820
  return projectResources;
2239
3821
  }
2240
3822
 
2241
- // src/prompts/index.ts
2242
- init_esm_shims();
2243
-
2244
3823
  // src/prompts/project-prompts.ts
2245
- init_esm_shims();
2246
3824
  var projectPrompts = [
2247
3825
  {
2248
3826
  name: "suggest-tasks",
@@ -2310,9 +3888,12 @@ var projectPrompts = [
2310
3888
  ]
2311
3889
  }
2312
3890
  ];
3891
+ function formatOptionalArgument(value) {
3892
+ return value && value.trim() ? value.trim() : "[not provided]";
3893
+ }
2313
3894
  function getRecommendNextTasksPrompt(projectId, limit) {
2314
- const projectHint = projectId ? `User specified project: ${projectId}. Use this project if it exists, or inform user if not found.` : "User did not specify a project. Based on the current working directory and conversation context, try to identify the most relevant project. If a clear match exists, focus on that project; otherwise, analyze all active projects";
2315
- const taskLimit = limit ? parseInt(limit, 10) : 3;
3895
+ const requestedProjectId = formatOptionalArgument(projectId);
3896
+ const requestedLimit = formatOptionalArgument(limit);
2316
3897
  return {
2317
3898
  description: "Intelligent Task Recommendation Assistant",
2318
3899
  messages: [
@@ -2320,7 +3901,17 @@ function getRecommendNextTasksPrompt(projectId, limit) {
2320
3901
  role: "user",
2321
3902
  content: {
2322
3903
  type: "text",
2323
- text: `${projectHint}, please recommend the ${taskLimit} tasks I should prioritize next.
3904
+ text: `Please recommend the next tasks I should prioritize.
3905
+
3906
+ ## Provided Arguments
3907
+ - projectId: ${requestedProjectId}
3908
+ - limit: ${requestedLimit}
3909
+
3910
+ ## Argument Handling Rules
3911
+ - If projectId is provided, use that project if it exists; if it does not exist, tell the user clearly.
3912
+ - If projectId is not provided, infer the most relevant project from the current working directory and conversation context; if no clear match exists, analyze all active projects.
3913
+ - If limit is provided and valid, return that many recommendations.
3914
+ - If limit is not provided or invalid, default to 3 recommendations.
2324
3915
 
2325
3916
  ## Recommendation Logic (sorted by priority):
2326
3917
 
@@ -2346,7 +3937,7 @@ function getRecommendNextTasksPrompt(projectId, limit) {
2346
3937
  2. **Filter high-priority tasks**: critical and high
2347
3938
  3. **Check due dates**: Identify tasks due soon or overdue
2348
3939
  4. **Analyze blocking relationships**: Identify tasks blocking others
2349
- 5. **Generate recommendation list**: Provide ${taskLimit} tasks to prioritize with reasons
3940
+ 5. **Generate recommendation list**: Provide the requested number of tasks to prioritize with reasons
2350
3941
 
2351
3942
  ## Output Format:
2352
3943
 
@@ -2380,7 +3971,7 @@ Keep task status synchronized with actual progress!`
2380
3971
  };
2381
3972
  }
2382
3973
  function getAutoPrioritizePrompt(projectId) {
2383
- const projectHint = projectId ? `User specified project: ${projectId}. Use this project if it exists, or inform user if not found.` : "User did not specify a project. Based on the current working directory and conversation context, try to identify the most relevant project. If a clear match exists, focus on that project; otherwise, analyze all active projects";
3974
+ const requestedProjectId = formatOptionalArgument(projectId);
2384
3975
  return {
2385
3976
  description: "Intelligent Priority Optimization Assistant",
2386
3977
  messages: [
@@ -2388,7 +3979,14 @@ function getAutoPrioritizePrompt(projectId) {
2388
3979
  role: "user",
2389
3980
  content: {
2390
3981
  type: "text",
2391
- text: `${projectHint}, please automatically analyze and adjust priorities.
3982
+ text: `Please automatically analyze and adjust priorities.
3983
+
3984
+ ## Provided Arguments
3985
+ - projectId: ${requestedProjectId}
3986
+
3987
+ ## Argument Handling Rules
3988
+ - If projectId is provided, use that project if it exists; if it does not exist, tell the user clearly.
3989
+ - If projectId is not provided, infer the most relevant project from the current working directory and conversation context; if no clear match exists, analyze all active projects.
2392
3990
 
2393
3991
  ## Priority Adjustment Rules:
2394
3992
 
@@ -2529,7 +4127,7 @@ Please fetch task info and immediately use update_task to apply the enhanced det
2529
4127
  };
2530
4128
  }
2531
4129
  function getQuickCapturePrompt(idea, projectId) {
2532
- const projectContext = projectId ? `User specified project: ${projectId}. Use this project if it exists, or inform user if not found.` : "User did not specify a project. Based on the current working directory and conversation context, try to identify the most relevant project. If a clear match exists, use that project; otherwise, analyze all active projects and select the best match";
4130
+ const requestedProjectId = formatOptionalArgument(projectId);
2533
4131
  return {
2534
4132
  description: "Quick Capture Assistant",
2535
4133
  messages: [
@@ -2539,7 +4137,12 @@ function getQuickCapturePrompt(idea, projectId) {
2539
4137
  type: "text",
2540
4138
  text: `I want to quickly capture an idea/task: "${idea}"
2541
4139
 
2542
- ${projectContext}
4140
+ ## Provided Arguments
4141
+ - projectId: ${requestedProjectId}
4142
+
4143
+ ## Argument Handling Rules
4144
+ - If projectId is provided, use that project if it exists; if it does not exist, tell the user clearly.
4145
+ - If projectId is not provided, infer the most relevant project from the current working directory and conversation context; if no clear match exists, analyze all active projects and choose the best fit.
2543
4146
 
2544
4147
  ## [Non-intrusive task] Pause your current work briefly to complete this quick capture, then let the user know it's done and resume what you were doing.
2545
4148
 
@@ -2603,7 +4206,7 @@ Analyze the idea, select the best matching project, and immediately execute crea
2603
4206
  };
2604
4207
  }
2605
4208
  function getOpenWebUIPrompt(port) {
2606
- const portNum = port ? parseInt(port, 10) : 7860;
4209
+ const requestedPort = formatOptionalArgument(port);
2607
4210
  return {
2608
4211
  description: "Open Web Visualization Interface",
2609
4212
  messages: [
@@ -2613,7 +4216,14 @@ function getOpenWebUIPrompt(port) {
2613
4216
  type: "text",
2614
4217
  text: `**[Non-intrusive task]** Pause your current work briefly to complete this quick action, then let the user know it's done and resume what you were doing.
2615
4218
 
2616
- Call the open_web_interface tool${port ? ` with port ${portNum}` : ""} to launch the roadmap-skill web visualization interface.`
4219
+ ## Provided Arguments
4220
+ - port: ${requestedPort}
4221
+
4222
+ ## Argument Handling Rules
4223
+ - If port is provided and valid, call the open_web_interface tool with that port.
4224
+ - If port is not provided or invalid, call the open_web_interface tool with its default behavior.
4225
+
4226
+ Launch the roadmap-skill web visualization interface now.`
2617
4227
  }
2618
4228
  }
2619
4229
  ]
@@ -2657,6 +4267,19 @@ var allTools = [
2657
4267
  updateTagTool,
2658
4268
  deleteTagTool,
2659
4269
  getTasksByTagTool,
4270
+ createDependencyViewTool,
4271
+ listDependencyViewsTool,
4272
+ getDependencyViewTool,
4273
+ updateDependencyViewTool,
4274
+ deleteDependencyViewTool,
4275
+ addTaskToDependencyViewTool,
4276
+ updateDependencyViewNodeTool,
4277
+ batchUpdateDependencyViewNodesTool,
4278
+ removeTaskFromDependencyViewTool,
4279
+ addDependencyViewEdgeTool,
4280
+ updateDependencyViewEdgeTool,
4281
+ removeDependencyViewEdgeTool,
4282
+ analyzeDependencyViewTool,
2660
4283
  openWebInterfaceTool,
2661
4284
  closeWebInterfaceTool
2662
4285
  ];