roadmap-skill 0.2.10 → 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/CHANGELOG.md +32 -0
- package/README.md +75 -36
- package/README.zh.md +37 -24
- package/dist/index.js +2132 -505
- package/dist/index.js.map +1 -1
- package/dist/web/app/assets/main-BZV40eAE.css +1 -0
- package/dist/web/app/assets/main-CewZLzdL.js +64 -0
- package/dist/web/app/index.html +2 -1
- package/dist/web/server.js +1631 -412
- package/dist/web/server.js.map +1 -1
- package/package.json +8 -3
- package/dist/web/app/assets/main-CuvRBjau.js +0 -9
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
|
-
//
|
|
13
|
-
import
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
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
|
-
|
|
614
|
-
|
|
615
|
-
|
|
652
|
+
result: {
|
|
653
|
+
success: true,
|
|
654
|
+
data: tag
|
|
655
|
+
},
|
|
656
|
+
shouldSave: true
|
|
616
657
|
};
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
if (!HEX_COLOR_PATTERN.test(resolvedColor)) {
|
|
658
|
+
});
|
|
659
|
+
if (result === null) {
|
|
620
660
|
return {
|
|
621
661
|
success: false,
|
|
622
|
-
error: `
|
|
623
|
-
code: "
|
|
662
|
+
error: `Project with ID '${projectId}' not found`,
|
|
663
|
+
code: "NOT_FOUND"
|
|
624
664
|
};
|
|
625
665
|
}
|
|
626
|
-
|
|
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
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
-
|
|
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: `
|
|
733
|
-
code: "
|
|
787
|
+
error: `Project with ID '${projectId}' not found`,
|
|
788
|
+
code: "NOT_FOUND"
|
|
734
789
|
};
|
|
735
790
|
}
|
|
736
|
-
|
|
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
|
|
769
|
-
|
|
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
|
-
|
|
772
|
-
|
|
773
|
-
|
|
835
|
+
result: {
|
|
836
|
+
success: true,
|
|
837
|
+
data: {
|
|
838
|
+
deleted: true,
|
|
839
|
+
tag,
|
|
840
|
+
tasksUpdated
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
shouldSave: true
|
|
774
844
|
};
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
if (tagIndex === -1) {
|
|
845
|
+
});
|
|
846
|
+
if (result === null) {
|
|
778
847
|
return {
|
|
779
848
|
success: false,
|
|
780
|
-
error: `
|
|
849
|
+
error: `Project with ID '${projectId}' not found`,
|
|
781
850
|
code: "NOT_FOUND"
|
|
782
851
|
};
|
|
783
852
|
}
|
|
784
|
-
|
|
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
|
|
901
|
-
|
|
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
|
-
|
|
904
|
-
|
|
905
|
-
|
|
967
|
+
result: {
|
|
968
|
+
success: true,
|
|
969
|
+
data: task
|
|
970
|
+
},
|
|
971
|
+
shouldSave: true
|
|
906
972
|
};
|
|
907
|
-
}
|
|
908
|
-
|
|
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: `
|
|
914
|
-
code: "
|
|
977
|
+
error: `Project with ID '${projectId}' not found`,
|
|
978
|
+
code: "NOT_FOUND"
|
|
915
979
|
};
|
|
916
980
|
}
|
|
917
|
-
|
|
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
|
|
995
|
-
|
|
996
|
-
|
|
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
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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
|
-
|
|
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
|
|
1070
|
-
|
|
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
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1139
|
+
result: {
|
|
1140
|
+
success: true,
|
|
1141
|
+
data: void 0
|
|
1142
|
+
},
|
|
1143
|
+
shouldSave: true
|
|
1075
1144
|
};
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
if (taskIndex === -1) {
|
|
1145
|
+
});
|
|
1146
|
+
if (result === null) {
|
|
1079
1147
|
return {
|
|
1080
1148
|
success: false,
|
|
1081
|
-
error: `
|
|
1149
|
+
error: `Project with ID '${projectId}' not found`,
|
|
1082
1150
|
code: "NOT_FOUND"
|
|
1083
1151
|
};
|
|
1084
1152
|
}
|
|
1085
|
-
|
|
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
|
|
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
|
|
1150
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
1402
|
-
|
|
1403
|
-
var
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
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
|
|
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
|
|
2749
|
+
return result;
|
|
1418
2750
|
}
|
|
1419
|
-
return {
|
|
2751
|
+
return {
|
|
2752
|
+
success: true,
|
|
2753
|
+
data: input.verbose ? result.data.view : toDependencyViewMutationResult(result.data)
|
|
2754
|
+
};
|
|
1420
2755
|
}
|
|
1421
2756
|
};
|
|
1422
|
-
var
|
|
1423
|
-
name: "
|
|
1424
|
-
description: "
|
|
1425
|
-
inputSchema:
|
|
1426
|
-
projectId:
|
|
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
|
|
2768
|
+
const { projectId, viewId, verbose, ...edgeData } = input;
|
|
2769
|
+
const result = await dependencyViewService.addEdge(projectId, viewId, edgeData);
|
|
1430
2770
|
if (!result.success) {
|
|
1431
|
-
return
|
|
2771
|
+
return result;
|
|
1432
2772
|
}
|
|
1433
|
-
return {
|
|
2773
|
+
return {
|
|
2774
|
+
success: true,
|
|
2775
|
+
data: verbose ? result.data.view : toDependencyViewMutationResult(result.data)
|
|
2776
|
+
};
|
|
1434
2777
|
}
|
|
1435
2778
|
};
|
|
1436
|
-
var
|
|
1437
|
-
name: "
|
|
1438
|
-
description: "
|
|
1439
|
-
inputSchema:
|
|
1440
|
-
projectId:
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
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
|
|
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
|
|
2791
|
+
return result;
|
|
1451
2792
|
}
|
|
1452
|
-
return {
|
|
2793
|
+
return {
|
|
2794
|
+
success: true,
|
|
2795
|
+
data: input.verbose ? result.data.view : toDependencyViewMutationResult(result.data)
|
|
2796
|
+
};
|
|
1453
2797
|
}
|
|
1454
2798
|
};
|
|
1455
|
-
var
|
|
1456
|
-
name: "
|
|
1457
|
-
description: "
|
|
1458
|
-
inputSchema:
|
|
1459
|
-
projectId:
|
|
1460
|
-
|
|
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
|
|
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
|
|
2814
|
+
return result;
|
|
1466
2815
|
}
|
|
1467
|
-
return {
|
|
2816
|
+
return {
|
|
2817
|
+
success: true,
|
|
2818
|
+
data: verbose ? result.data.view : toDependencyViewMutationResult(result.data)
|
|
2819
|
+
};
|
|
1468
2820
|
}
|
|
1469
2821
|
};
|
|
1470
|
-
var
|
|
1471
|
-
name: "
|
|
1472
|
-
description: "
|
|
1473
|
-
inputSchema:
|
|
1474
|
-
projectId:
|
|
1475
|
-
|
|
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
|
-
|
|
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
|
|
2837
|
+
import { fileURLToPath } from "url";
|
|
1494
2838
|
import { createRequire } from "module";
|
|
1495
2839
|
import { existsSync } from "fs";
|
|
1496
|
-
var __filename2 =
|
|
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
|
-
|
|
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:
|
|
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:
|
|
1903
|
-
templateName:
|
|
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:
|
|
1942
|
-
templateName:
|
|
1943
|
-
projectName:
|
|
1944
|
-
description:
|
|
1945
|
-
startDate:
|
|
1946
|
-
targetDate:
|
|
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
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
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:
|
|
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
|
|
2315
|
-
const
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
4130
|
+
const requestedProjectId = formatOptionalArgument(projectId);
|
|
2533
4131
|
return {
|
|
2534
4132
|
description: "Quick Capture Assistant",
|
|
2535
4133
|
messages: [
|
|
@@ -2539,7 +4137,14 @@ function getQuickCapturePrompt(idea, projectId) {
|
|
|
2539
4137
|
type: "text",
|
|
2540
4138
|
text: `I want to quickly capture an idea/task: "${idea}"
|
|
2541
4139
|
|
|
2542
|
-
|
|
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.
|
|
4146
|
+
|
|
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.
|
|
2543
4148
|
|
|
2544
4149
|
## Please complete the following steps immediately:
|
|
2545
4150
|
|
|
@@ -2601,7 +4206,7 @@ Analyze the idea, select the best matching project, and immediately execute crea
|
|
|
2601
4206
|
};
|
|
2602
4207
|
}
|
|
2603
4208
|
function getOpenWebUIPrompt(port) {
|
|
2604
|
-
const
|
|
4209
|
+
const requestedPort = formatOptionalArgument(port);
|
|
2605
4210
|
return {
|
|
2606
4211
|
description: "Open Web Visualization Interface",
|
|
2607
4212
|
messages: [
|
|
@@ -2609,7 +4214,16 @@ function getOpenWebUIPrompt(port) {
|
|
|
2609
4214
|
role: "user",
|
|
2610
4215
|
content: {
|
|
2611
4216
|
type: "text",
|
|
2612
|
-
text:
|
|
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.
|
|
4218
|
+
|
|
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.`
|
|
2613
4227
|
}
|
|
2614
4228
|
}
|
|
2615
4229
|
]
|
|
@@ -2653,6 +4267,19 @@ var allTools = [
|
|
|
2653
4267
|
updateTagTool,
|
|
2654
4268
|
deleteTagTool,
|
|
2655
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,
|
|
2656
4283
|
openWebInterfaceTool,
|
|
2657
4284
|
closeWebInterfaceTool
|
|
2658
4285
|
];
|