@howaboua/opencode-roadmap-plugin 0.1.6 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/descriptions/createroadmap.txt +11 -7
- package/dist/src/descriptions/readroadmap.txt +3 -3
- package/dist/src/descriptions/updateroadmap.txt +1 -1
- package/dist/src/errors/roadmap_corrupted.txt +1 -1
- package/dist/src/roadmap/document.d.ts +9 -0
- package/dist/src/roadmap/document.js +117 -0
- package/dist/src/roadmap/files.d.ts +4 -0
- package/dist/src/roadmap/files.js +49 -0
- package/dist/src/roadmap/lock.d.ts +1 -0
- package/dist/src/roadmap/lock.js +33 -0
- package/dist/src/roadmap/paths.d.ts +7 -0
- package/dist/src/roadmap/paths.js +12 -0
- package/dist/src/storage.d.ts +11 -20
- package/dist/src/storage.js +50 -203
- package/dist/src/tools/createroadmap.js +113 -99
- package/dist/src/tools/readroadmap.js +8 -4
- package/dist/src/tools/updateroadmap.js +93 -82
- package/dist/src/types.d.ts +9 -2
- package/dist/src/validators.d.ts +21 -0
- package/dist/src/validators.js +135 -0
- package/package.json +1 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export class RoadmapValidator {
|
|
2
|
+
static validateFeatureNumber(number) {
|
|
3
|
+
if (!number || typeof number !== "string") {
|
|
4
|
+
return {
|
|
5
|
+
code: "INVALID_FEATURE_NUMBER",
|
|
6
|
+
message: "Invalid feature ID: must be a string.",
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
if (!/^\d+$/.test(number)) {
|
|
10
|
+
return {
|
|
11
|
+
code: "INVALID_FEATURE_NUMBER_FORMAT",
|
|
12
|
+
message: "Invalid feature ID format: must be a simple number.",
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
static validateActionNumber(number) {
|
|
18
|
+
if (!number || typeof number !== "string") {
|
|
19
|
+
return {
|
|
20
|
+
code: "INVALID_ACTION_NUMBER",
|
|
21
|
+
message: "Invalid action ID: must be a string.",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (!/^\d+\.\d{2}$/.test(number)) {
|
|
25
|
+
return {
|
|
26
|
+
code: "INVALID_ACTION_NUMBER_FORMAT",
|
|
27
|
+
message: "Invalid action ID format: must be X.YY (e.g., 1.01).",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
static validateActionSequence(actions, globalSeenNumbers, featureNumber) {
|
|
33
|
+
const errors = [];
|
|
34
|
+
const seenNumbers = new Set();
|
|
35
|
+
for (const action of actions) {
|
|
36
|
+
const numberError = this.validateActionNumber(action.number);
|
|
37
|
+
if (numberError) {
|
|
38
|
+
errors.push(numberError);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (featureNumber) {
|
|
42
|
+
const actionFeaturePrefix = action.number.split(".")[0];
|
|
43
|
+
if (actionFeaturePrefix !== featureNumber) {
|
|
44
|
+
errors.push({
|
|
45
|
+
code: "ACTION_FEATURE_MISMATCH",
|
|
46
|
+
message: `Action "${action.number}" does not belong to feature "${featureNumber}".`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (seenNumbers.has(action.number)) {
|
|
51
|
+
errors.push({
|
|
52
|
+
code: "DUPLICATE_ACTION_NUMBER",
|
|
53
|
+
message: `Duplicate action ID "${action.number}".`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (globalSeenNumbers?.has(action.number)) {
|
|
57
|
+
errors.push({
|
|
58
|
+
code: "DUPLICATE_ACTION_NUMBER_GLOBAL",
|
|
59
|
+
message: `Duplicate action ID "${action.number}" (exists in another feature).`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
seenNumbers.add(action.number);
|
|
63
|
+
globalSeenNumbers?.add(action.number);
|
|
64
|
+
}
|
|
65
|
+
return errors;
|
|
66
|
+
}
|
|
67
|
+
static validateFeatureSequence(features) {
|
|
68
|
+
const errors = [];
|
|
69
|
+
const seenNumbers = new Set();
|
|
70
|
+
const seenActionNumbers = new Set();
|
|
71
|
+
for (const feature of features) {
|
|
72
|
+
const numberError = this.validateFeatureNumber(feature.number);
|
|
73
|
+
if (numberError) {
|
|
74
|
+
errors.push(numberError);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (seenNumbers.has(feature.number)) {
|
|
78
|
+
errors.push({
|
|
79
|
+
code: "DUPLICATE_FEATURE_NUMBER",
|
|
80
|
+
message: `Duplicate feature ID "${feature.number}".`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
seenNumbers.add(feature.number);
|
|
84
|
+
const actionErrors = this.validateActionSequence(feature.actions, seenActionNumbers, feature.number);
|
|
85
|
+
errors.push(...actionErrors);
|
|
86
|
+
}
|
|
87
|
+
return errors;
|
|
88
|
+
}
|
|
89
|
+
static validateTitle(title, fieldType) {
|
|
90
|
+
if (!title || typeof title !== "string") {
|
|
91
|
+
return {
|
|
92
|
+
code: "INVALID_TITLE",
|
|
93
|
+
message: `Invalid ${fieldType} title. Must be non-empty string.`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (title.trim() === "") {
|
|
97
|
+
return {
|
|
98
|
+
code: "EMPTY_TITLE",
|
|
99
|
+
message: `${fieldType.charAt(0).toUpperCase() + fieldType.slice(1)} title cannot be empty.`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
static validateDescription(description, fieldType) {
|
|
105
|
+
if (!description || typeof description !== "string") {
|
|
106
|
+
return {
|
|
107
|
+
code: "INVALID_DESCRIPTION",
|
|
108
|
+
message: `Invalid ${fieldType} description. Must be non-empty string.`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (description.trim() === "") {
|
|
112
|
+
return {
|
|
113
|
+
code: "EMPTY_DESCRIPTION",
|
|
114
|
+
message: `${fieldType.charAt(0).toUpperCase() + fieldType.slice(1)} description cannot be empty.`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
static validateStatusProgression(currentStatus, newStatus) {
|
|
120
|
+
const validStatuses = ["pending", "in_progress", "completed", "cancelled"];
|
|
121
|
+
if (!validStatuses.includes(newStatus)) {
|
|
122
|
+
return {
|
|
123
|
+
code: "INVALID_STATUS",
|
|
124
|
+
message: `Invalid status "${newStatus}". Valid: ${validStatuses.join(", ")}`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (currentStatus === "cancelled") {
|
|
128
|
+
return {
|
|
129
|
+
code: "INVALID_STATUS_TRANSITION",
|
|
130
|
+
message: "Cannot change status of cancelled action. Create a new action instead.",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|