@flowdot.ai/daemon 1.0.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/LICENSE +45 -0
- package/README.md +51 -0
- package/dist/goals/DependencyResolver.d.ts +54 -0
- package/dist/goals/DependencyResolver.js +329 -0
- package/dist/goals/ErrorRecovery.d.ts +133 -0
- package/dist/goals/ErrorRecovery.js +489 -0
- package/dist/goals/GoalApiClient.d.ts +81 -0
- package/dist/goals/GoalApiClient.js +743 -0
- package/dist/goals/GoalCache.d.ts +65 -0
- package/dist/goals/GoalCache.js +243 -0
- package/dist/goals/GoalCommsHandler.d.ts +150 -0
- package/dist/goals/GoalCommsHandler.js +378 -0
- package/dist/goals/GoalExporter.d.ts +164 -0
- package/dist/goals/GoalExporter.js +318 -0
- package/dist/goals/GoalImporter.d.ts +107 -0
- package/dist/goals/GoalImporter.js +345 -0
- package/dist/goals/GoalManager.d.ts +110 -0
- package/dist/goals/GoalManager.js +535 -0
- package/dist/goals/GoalReporter.d.ts +105 -0
- package/dist/goals/GoalReporter.js +534 -0
- package/dist/goals/GoalScheduler.d.ts +102 -0
- package/dist/goals/GoalScheduler.js +209 -0
- package/dist/goals/GoalValidator.d.ts +72 -0
- package/dist/goals/GoalValidator.js +657 -0
- package/dist/goals/MetaGoalEnforcer.d.ts +111 -0
- package/dist/goals/MetaGoalEnforcer.js +536 -0
- package/dist/goals/MilestoneBreaker.d.ts +74 -0
- package/dist/goals/MilestoneBreaker.js +348 -0
- package/dist/goals/PermissionBridge.d.ts +109 -0
- package/dist/goals/PermissionBridge.js +326 -0
- package/dist/goals/ProgressTracker.d.ts +113 -0
- package/dist/goals/ProgressTracker.js +324 -0
- package/dist/goals/ReviewScheduler.d.ts +106 -0
- package/dist/goals/ReviewScheduler.js +360 -0
- package/dist/goals/TaskExecutor.d.ts +116 -0
- package/dist/goals/TaskExecutor.js +370 -0
- package/dist/goals/TaskFeedback.d.ts +126 -0
- package/dist/goals/TaskFeedback.js +402 -0
- package/dist/goals/TaskGenerator.d.ts +75 -0
- package/dist/goals/TaskGenerator.js +329 -0
- package/dist/goals/TaskQueue.d.ts +84 -0
- package/dist/goals/TaskQueue.js +331 -0
- package/dist/goals/TaskSanitizer.d.ts +61 -0
- package/dist/goals/TaskSanitizer.js +464 -0
- package/dist/goals/errors.d.ts +116 -0
- package/dist/goals/errors.js +299 -0
- package/dist/goals/index.d.ts +24 -0
- package/dist/goals/index.js +23 -0
- package/dist/goals/types.d.ts +395 -0
- package/dist/goals/types.js +230 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/loop/DaemonIPC.d.ts +67 -0
- package/dist/loop/DaemonIPC.js +358 -0
- package/dist/loop/IntervalParser.d.ts +39 -0
- package/dist/loop/IntervalParser.js +217 -0
- package/dist/loop/LoopDaemon.d.ts +123 -0
- package/dist/loop/LoopDaemon.js +1821 -0
- package/dist/loop/LoopExecutor.d.ts +93 -0
- package/dist/loop/LoopExecutor.js +326 -0
- package/dist/loop/LoopManager.d.ts +79 -0
- package/dist/loop/LoopManager.js +476 -0
- package/dist/loop/LoopScheduler.d.ts +69 -0
- package/dist/loop/LoopScheduler.js +329 -0
- package/dist/loop/LoopStore.d.ts +57 -0
- package/dist/loop/LoopStore.js +406 -0
- package/dist/loop/LoopValidator.d.ts +55 -0
- package/dist/loop/LoopValidator.js +603 -0
- package/dist/loop/errors.d.ts +115 -0
- package/dist/loop/errors.js +312 -0
- package/dist/loop/index.d.ts +11 -0
- package/dist/loop/index.js +10 -0
- package/dist/loop/notifications/Notifier.d.ts +28 -0
- package/dist/loop/notifications/Notifier.js +78 -0
- package/dist/loop/notifications/SlackNotifier.d.ts +28 -0
- package/dist/loop/notifications/SlackNotifier.js +203 -0
- package/dist/loop/notifications/TerminalNotifier.d.ts +18 -0
- package/dist/loop/notifications/TerminalNotifier.js +72 -0
- package/dist/loop/notifications/WebhookNotifier.d.ts +24 -0
- package/dist/loop/notifications/WebhookNotifier.js +123 -0
- package/dist/loop/notifications/index.d.ts +24 -0
- package/dist/loop/notifications/index.js +109 -0
- package/dist/loop/types.d.ts +280 -0
- package/dist/loop/types.js +222 -0
- package/package.json +92 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import { isGoalPriority, isApprovalMode, isTaskType, isMemoryType, isMemorySource, isFeedbackQuality, isValidGoalHash, } from './types.js';
|
|
2
|
+
import { ValidationError } from './errors.js';
|
|
3
|
+
export const DEFAULT_GOAL_VALIDATION_CONSTRAINTS = {
|
|
4
|
+
maxNameLength: 255,
|
|
5
|
+
minNameLength: 1,
|
|
6
|
+
maxDescriptionLength: 5000,
|
|
7
|
+
maxTitleLength: 255,
|
|
8
|
+
minTitleLength: 1,
|
|
9
|
+
maxMemoryContentLength: 2000,
|
|
10
|
+
minMemoryContentLength: 1,
|
|
11
|
+
maxMetaGoalDescriptionLength: 1000,
|
|
12
|
+
minMetaGoalDescriptionLength: 1,
|
|
13
|
+
maxMetaGoalPriority: 100,
|
|
14
|
+
minMetaGoalPriority: 0,
|
|
15
|
+
maxFeedbackCommentLength: 1000,
|
|
16
|
+
maxDependencies: 50,
|
|
17
|
+
namePattern: /^[a-zA-Z0-9][a-zA-Z0-9\s_-]*$/,
|
|
18
|
+
};
|
|
19
|
+
export class GoalValidator {
|
|
20
|
+
constraints;
|
|
21
|
+
constructor(constraints = {}) {
|
|
22
|
+
this.constraints = { ...DEFAULT_GOAL_VALIDATION_CONSTRAINTS, ...constraints };
|
|
23
|
+
}
|
|
24
|
+
validateCreateGoalInput(input) {
|
|
25
|
+
const result = this.validateCreateGoalInputSafe(input);
|
|
26
|
+
if (!result.valid) {
|
|
27
|
+
throw new ValidationError('Goal validation failed', this.issuesToRecord(result.errors));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
validateCreateGoalInputSafe(input) {
|
|
31
|
+
const errors = [];
|
|
32
|
+
if (!input.name) {
|
|
33
|
+
errors.push({ field: 'name', message: 'Name is required' });
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.validateName(input.name, errors);
|
|
37
|
+
}
|
|
38
|
+
if (input.description !== undefined) {
|
|
39
|
+
this.validateDescription(input.description, errors);
|
|
40
|
+
}
|
|
41
|
+
if (input.priority !== undefined && !isGoalPriority(input.priority)) {
|
|
42
|
+
errors.push({
|
|
43
|
+
field: 'priority',
|
|
44
|
+
message: 'Priority must be one of: high, medium, low',
|
|
45
|
+
value: input.priority,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (input.approvalMode !== undefined && !isApprovalMode(input.approvalMode)) {
|
|
49
|
+
errors.push({
|
|
50
|
+
field: 'approvalMode',
|
|
51
|
+
message: 'Approval mode must be one of: full, category, trusted',
|
|
52
|
+
value: input.approvalMode,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (input.deadline !== undefined) {
|
|
56
|
+
this.validateDateString(input.deadline, 'deadline', errors);
|
|
57
|
+
}
|
|
58
|
+
if (input.parentHash !== undefined && !isValidGoalHash(input.parentHash)) {
|
|
59
|
+
errors.push({
|
|
60
|
+
field: 'parentHash',
|
|
61
|
+
message: 'Invalid parent goal hash',
|
|
62
|
+
value: input.parentHash,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (input.dependsOn !== undefined) {
|
|
66
|
+
this.validateDependencies(input.dependsOn, errors);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
valid: errors.length === 0,
|
|
70
|
+
errors,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
validateUpdateGoalInput(input) {
|
|
74
|
+
const result = this.validateUpdateGoalInputSafe(input);
|
|
75
|
+
if (!result.valid) {
|
|
76
|
+
throw new ValidationError('Goal update validation failed', this.issuesToRecord(result.errors));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
validateUpdateGoalInputSafe(input) {
|
|
80
|
+
const errors = [];
|
|
81
|
+
const hasAnyField = input.name !== undefined ||
|
|
82
|
+
input.description !== undefined ||
|
|
83
|
+
input.priority !== undefined ||
|
|
84
|
+
input.approvalMode !== undefined ||
|
|
85
|
+
input.deadline !== undefined ||
|
|
86
|
+
input.restrictions !== undefined ||
|
|
87
|
+
input.schedule !== undefined ||
|
|
88
|
+
input.notificationPreferences !== undefined ||
|
|
89
|
+
input.reviewSchedule !== undefined ||
|
|
90
|
+
input.dependsOn !== undefined;
|
|
91
|
+
if (!hasAnyField) {
|
|
92
|
+
errors.push({ field: '_root', message: 'At least one field must be provided for update' });
|
|
93
|
+
}
|
|
94
|
+
if (input.name !== undefined) {
|
|
95
|
+
this.validateName(input.name, errors);
|
|
96
|
+
}
|
|
97
|
+
if (input.description !== undefined) {
|
|
98
|
+
this.validateDescription(input.description, errors);
|
|
99
|
+
}
|
|
100
|
+
if (input.priority !== undefined && !isGoalPriority(input.priority)) {
|
|
101
|
+
errors.push({
|
|
102
|
+
field: 'priority',
|
|
103
|
+
message: 'Priority must be one of: high, medium, low',
|
|
104
|
+
value: input.priority,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (input.approvalMode !== undefined && !isApprovalMode(input.approvalMode)) {
|
|
108
|
+
errors.push({
|
|
109
|
+
field: 'approvalMode',
|
|
110
|
+
message: 'Approval mode must be one of: full, category, trusted',
|
|
111
|
+
value: input.approvalMode,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (input.deadline !== undefined) {
|
|
115
|
+
this.validateDateString(input.deadline, 'deadline', errors);
|
|
116
|
+
}
|
|
117
|
+
if (input.dependsOn !== undefined) {
|
|
118
|
+
this.validateDependencies(input.dependsOn, errors);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
valid: errors.length === 0,
|
|
122
|
+
errors,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
validateCreateMilestoneInput(input) {
|
|
126
|
+
const result = this.validateCreateMilestoneInputSafe(input);
|
|
127
|
+
if (!result.valid) {
|
|
128
|
+
throw new ValidationError('Milestone validation failed', this.issuesToRecord(result.errors));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
validateCreateMilestoneInputSafe(input) {
|
|
132
|
+
const errors = [];
|
|
133
|
+
if (!input.title) {
|
|
134
|
+
errors.push({ field: 'title', message: 'Title is required' });
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
this.validateTitle(input.title, errors);
|
|
138
|
+
}
|
|
139
|
+
if (input.description !== undefined) {
|
|
140
|
+
this.validateDescription(input.description, errors);
|
|
141
|
+
}
|
|
142
|
+
if (input.targetDate !== undefined) {
|
|
143
|
+
this.validateDateString(input.targetDate, 'targetDate', errors);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
valid: errors.length === 0,
|
|
147
|
+
errors,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
validateUpdateMilestoneInput(input) {
|
|
151
|
+
const result = this.validateUpdateMilestoneInputSafe(input);
|
|
152
|
+
if (!result.valid) {
|
|
153
|
+
throw new ValidationError('Milestone update validation failed', this.issuesToRecord(result.errors));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
validateUpdateMilestoneInputSafe(input) {
|
|
157
|
+
const errors = [];
|
|
158
|
+
const hasAnyField = input.title !== undefined ||
|
|
159
|
+
input.description !== undefined ||
|
|
160
|
+
input.targetDate !== undefined;
|
|
161
|
+
if (!hasAnyField) {
|
|
162
|
+
errors.push({ field: '_root', message: 'At least one field must be provided for update' });
|
|
163
|
+
}
|
|
164
|
+
if (input.title !== undefined) {
|
|
165
|
+
this.validateTitle(input.title, errors);
|
|
166
|
+
}
|
|
167
|
+
if (input.description !== undefined) {
|
|
168
|
+
this.validateDescription(input.description, errors);
|
|
169
|
+
}
|
|
170
|
+
if (input.targetDate !== undefined) {
|
|
171
|
+
this.validateDateString(input.targetDate, 'targetDate', errors);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
valid: errors.length === 0,
|
|
175
|
+
errors,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
validateCreateTaskInput(input) {
|
|
179
|
+
const result = this.validateCreateTaskInputSafe(input);
|
|
180
|
+
if (!result.valid) {
|
|
181
|
+
throw new ValidationError('Task validation failed', this.issuesToRecord(result.errors));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
validateCreateTaskInputSafe(input) {
|
|
185
|
+
const errors = [];
|
|
186
|
+
if (!input.title) {
|
|
187
|
+
errors.push({ field: 'title', message: 'Title is required' });
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
this.validateTitle(input.title, errors);
|
|
191
|
+
}
|
|
192
|
+
if (input.description !== undefined) {
|
|
193
|
+
this.validateDescription(input.description, errors);
|
|
194
|
+
}
|
|
195
|
+
if (input.taskType !== undefined && !isTaskType(input.taskType)) {
|
|
196
|
+
errors.push({
|
|
197
|
+
field: 'taskType',
|
|
198
|
+
message: 'Task type must be one of: research, draft, code, execute, recipe, loop, notify',
|
|
199
|
+
value: input.taskType,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (input.scheduledFor !== undefined) {
|
|
203
|
+
this.validateDateString(input.scheduledFor, 'scheduledFor', errors);
|
|
204
|
+
}
|
|
205
|
+
if (input.milestoneId !== undefined && typeof input.milestoneId !== 'number') {
|
|
206
|
+
errors.push({
|
|
207
|
+
field: 'milestoneId',
|
|
208
|
+
message: 'Milestone ID must be a number',
|
|
209
|
+
value: input.milestoneId,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
valid: errors.length === 0,
|
|
214
|
+
errors,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
validateUpdateTaskInput(input) {
|
|
218
|
+
const result = this.validateUpdateTaskInputSafe(input);
|
|
219
|
+
if (!result.valid) {
|
|
220
|
+
throw new ValidationError('Task update validation failed', this.issuesToRecord(result.errors));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
validateUpdateTaskInputSafe(input) {
|
|
224
|
+
const errors = [];
|
|
225
|
+
const hasAnyField = input.title !== undefined ||
|
|
226
|
+
input.description !== undefined ||
|
|
227
|
+
input.taskType !== undefined ||
|
|
228
|
+
input.permissionCategory !== undefined ||
|
|
229
|
+
input.scheduledFor !== undefined ||
|
|
230
|
+
input.milestoneId !== undefined;
|
|
231
|
+
if (!hasAnyField) {
|
|
232
|
+
errors.push({ field: '_root', message: 'At least one field must be provided for update' });
|
|
233
|
+
}
|
|
234
|
+
if (input.title !== undefined) {
|
|
235
|
+
this.validateTitle(input.title, errors);
|
|
236
|
+
}
|
|
237
|
+
if (input.description !== undefined) {
|
|
238
|
+
this.validateDescription(input.description, errors);
|
|
239
|
+
}
|
|
240
|
+
if (input.taskType !== undefined && !isTaskType(input.taskType)) {
|
|
241
|
+
errors.push({
|
|
242
|
+
field: 'taskType',
|
|
243
|
+
message: 'Task type must be one of: research, draft, code, execute, recipe, loop, notify',
|
|
244
|
+
value: input.taskType,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (input.scheduledFor !== undefined) {
|
|
248
|
+
this.validateDateString(input.scheduledFor, 'scheduledFor', errors);
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
valid: errors.length === 0,
|
|
252
|
+
errors,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
validateTaskFeedbackInput(input) {
|
|
256
|
+
const result = this.validateTaskFeedbackInputSafe(input);
|
|
257
|
+
if (!result.valid) {
|
|
258
|
+
throw new ValidationError('Task feedback validation failed', this.issuesToRecord(result.errors));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
validateTaskFeedbackInputSafe(input) {
|
|
262
|
+
const errors = [];
|
|
263
|
+
if (!isFeedbackQuality(input.quality)) {
|
|
264
|
+
errors.push({
|
|
265
|
+
field: 'quality',
|
|
266
|
+
message: 'Quality must be one of: high, medium, low',
|
|
267
|
+
value: input.quality,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (input.comment !== undefined) {
|
|
271
|
+
if (typeof input.comment !== 'string') {
|
|
272
|
+
errors.push({
|
|
273
|
+
field: 'comment',
|
|
274
|
+
message: 'Comment must be a string',
|
|
275
|
+
value: input.comment,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
else if (input.comment.length > this.constraints.maxFeedbackCommentLength) {
|
|
279
|
+
errors.push({
|
|
280
|
+
field: 'comment',
|
|
281
|
+
message: `Comment must be at most ${this.constraints.maxFeedbackCommentLength} characters`,
|
|
282
|
+
value: input.comment.length,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
valid: errors.length === 0,
|
|
288
|
+
errors,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
validateCreateMemoryInput(input) {
|
|
292
|
+
const result = this.validateCreateMemoryInputSafe(input);
|
|
293
|
+
if (!result.valid) {
|
|
294
|
+
throw new ValidationError('Memory validation failed', this.issuesToRecord(result.errors));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
validateCreateMemoryInputSafe(input) {
|
|
298
|
+
const errors = [];
|
|
299
|
+
if (!input.content) {
|
|
300
|
+
errors.push({ field: 'content', message: 'Content is required' });
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
this.validateMemoryContent(input.content, errors);
|
|
304
|
+
}
|
|
305
|
+
if (input.memoryType !== undefined && !isMemoryType(input.memoryType)) {
|
|
306
|
+
errors.push({
|
|
307
|
+
field: 'memoryType',
|
|
308
|
+
message: 'Memory type must be one of: conversation, decision, learning',
|
|
309
|
+
value: input.memoryType,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
if (input.source !== undefined && !isMemorySource(input.source)) {
|
|
313
|
+
errors.push({
|
|
314
|
+
field: 'source',
|
|
315
|
+
message: 'Source must be one of: manual, auto',
|
|
316
|
+
value: input.source,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
valid: errors.length === 0,
|
|
321
|
+
errors,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
validateUpdateMemoryInput(input) {
|
|
325
|
+
const result = this.validateUpdateMemoryInputSafe(input);
|
|
326
|
+
if (!result.valid) {
|
|
327
|
+
throw new ValidationError('Memory update validation failed', this.issuesToRecord(result.errors));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
validateUpdateMemoryInputSafe(input) {
|
|
331
|
+
const errors = [];
|
|
332
|
+
const hasAnyField = input.content !== undefined ||
|
|
333
|
+
input.memoryType !== undefined ||
|
|
334
|
+
input.isEnabled !== undefined;
|
|
335
|
+
if (!hasAnyField) {
|
|
336
|
+
errors.push({ field: '_root', message: 'At least one field must be provided for update' });
|
|
337
|
+
}
|
|
338
|
+
if (input.content !== undefined) {
|
|
339
|
+
this.validateMemoryContent(input.content, errors);
|
|
340
|
+
}
|
|
341
|
+
if (input.memoryType !== undefined && !isMemoryType(input.memoryType)) {
|
|
342
|
+
errors.push({
|
|
343
|
+
field: 'memoryType',
|
|
344
|
+
message: 'Memory type must be one of: conversation, decision, learning',
|
|
345
|
+
value: input.memoryType,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (input.isEnabled !== undefined && typeof input.isEnabled !== 'boolean') {
|
|
349
|
+
errors.push({
|
|
350
|
+
field: 'isEnabled',
|
|
351
|
+
message: 'isEnabled must be a boolean',
|
|
352
|
+
value: input.isEnabled,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
valid: errors.length === 0,
|
|
357
|
+
errors,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
validateCreateMetaGoalInput(input) {
|
|
361
|
+
const result = this.validateCreateMetaGoalInputSafe(input);
|
|
362
|
+
if (!result.valid) {
|
|
363
|
+
throw new ValidationError('Meta-goal validation failed', this.issuesToRecord(result.errors));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
validateCreateMetaGoalInputSafe(input) {
|
|
367
|
+
const errors = [];
|
|
368
|
+
if (!input.description) {
|
|
369
|
+
errors.push({ field: 'description', message: 'Description is required' });
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
this.validateMetaGoalDescription(input.description, errors);
|
|
373
|
+
}
|
|
374
|
+
if (input.priority !== undefined) {
|
|
375
|
+
this.validateMetaGoalPriority(input.priority, errors);
|
|
376
|
+
}
|
|
377
|
+
if (input.scope !== undefined) {
|
|
378
|
+
this.validateMetaGoalScope(input.scope, errors);
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
valid: errors.length === 0,
|
|
382
|
+
errors,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
validateUpdateMetaGoalInput(input) {
|
|
386
|
+
const result = this.validateUpdateMetaGoalInputSafe(input);
|
|
387
|
+
if (!result.valid) {
|
|
388
|
+
throw new ValidationError('Meta-goal update validation failed', this.issuesToRecord(result.errors));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
validateUpdateMetaGoalInputSafe(input) {
|
|
392
|
+
const errors = [];
|
|
393
|
+
const hasAnyField = input.description !== undefined ||
|
|
394
|
+
input.priority !== undefined ||
|
|
395
|
+
input.scope !== undefined ||
|
|
396
|
+
input.isActive !== undefined;
|
|
397
|
+
if (!hasAnyField) {
|
|
398
|
+
errors.push({ field: '_root', message: 'At least one field must be provided for update' });
|
|
399
|
+
}
|
|
400
|
+
if (input.description !== undefined) {
|
|
401
|
+
this.validateMetaGoalDescription(input.description, errors);
|
|
402
|
+
}
|
|
403
|
+
if (input.priority !== undefined) {
|
|
404
|
+
this.validateMetaGoalPriority(input.priority, errors);
|
|
405
|
+
}
|
|
406
|
+
if (input.scope !== undefined) {
|
|
407
|
+
this.validateMetaGoalScope(input.scope, errors);
|
|
408
|
+
}
|
|
409
|
+
if (input.isActive !== undefined && typeof input.isActive !== 'boolean') {
|
|
410
|
+
errors.push({
|
|
411
|
+
field: 'isActive',
|
|
412
|
+
message: 'isActive must be a boolean',
|
|
413
|
+
value: input.isActive,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
valid: errors.length === 0,
|
|
418
|
+
errors,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
validateGoalHash(hash) {
|
|
422
|
+
if (!isValidGoalHash(hash)) {
|
|
423
|
+
throw new ValidationError('Invalid goal hash', {
|
|
424
|
+
hash: ['Goal hash must be a non-empty string'],
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
validateName(name, errors) {
|
|
429
|
+
if (name.length < this.constraints.minNameLength) {
|
|
430
|
+
errors.push({
|
|
431
|
+
field: 'name',
|
|
432
|
+
message: `Name must be at least ${this.constraints.minNameLength} character(s)`,
|
|
433
|
+
value: name.length,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
else if (name.length > this.constraints.maxNameLength) {
|
|
437
|
+
errors.push({
|
|
438
|
+
field: 'name',
|
|
439
|
+
message: `Name must be at most ${this.constraints.maxNameLength} characters`,
|
|
440
|
+
value: name.length,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
else if (!this.constraints.namePattern.test(name)) {
|
|
444
|
+
errors.push({
|
|
445
|
+
field: 'name',
|
|
446
|
+
message: 'Name must start with alphanumeric and contain only alphanumeric characters, spaces, underscores, or hyphens',
|
|
447
|
+
value: name,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
validateTitle(title, errors) {
|
|
452
|
+
if (title.length < this.constraints.minTitleLength) {
|
|
453
|
+
errors.push({
|
|
454
|
+
field: 'title',
|
|
455
|
+
message: `Title must be at least ${this.constraints.minTitleLength} character(s)`,
|
|
456
|
+
value: title.length,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
else if (title.length > this.constraints.maxTitleLength) {
|
|
460
|
+
errors.push({
|
|
461
|
+
field: 'title',
|
|
462
|
+
message: `Title must be at most ${this.constraints.maxTitleLength} characters`,
|
|
463
|
+
value: title.length,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
validateDescription(description, errors) {
|
|
468
|
+
if (typeof description !== 'string') {
|
|
469
|
+
errors.push({
|
|
470
|
+
field: 'description',
|
|
471
|
+
message: 'Description must be a string',
|
|
472
|
+
value: typeof description,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
else if (description.length > this.constraints.maxDescriptionLength) {
|
|
476
|
+
errors.push({
|
|
477
|
+
field: 'description',
|
|
478
|
+
message: `Description must be at most ${this.constraints.maxDescriptionLength} characters`,
|
|
479
|
+
value: description.length,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
validateMemoryContent(content, errors) {
|
|
484
|
+
if (typeof content !== 'string') {
|
|
485
|
+
errors.push({
|
|
486
|
+
field: 'content',
|
|
487
|
+
message: 'Content must be a string',
|
|
488
|
+
value: typeof content,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
else if (content.length < this.constraints.minMemoryContentLength) {
|
|
492
|
+
errors.push({
|
|
493
|
+
field: 'content',
|
|
494
|
+
message: `Content must be at least ${this.constraints.minMemoryContentLength} character(s)`,
|
|
495
|
+
value: content.length,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
else if (content.length > this.constraints.maxMemoryContentLength) {
|
|
499
|
+
errors.push({
|
|
500
|
+
field: 'content',
|
|
501
|
+
message: `Content must be at most ${this.constraints.maxMemoryContentLength} characters`,
|
|
502
|
+
value: content.length,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
validateMetaGoalDescription(description, errors) {
|
|
507
|
+
if (typeof description !== 'string') {
|
|
508
|
+
errors.push({
|
|
509
|
+
field: 'description',
|
|
510
|
+
message: 'Description must be a string',
|
|
511
|
+
value: typeof description,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
else if (description.length < this.constraints.minMetaGoalDescriptionLength) {
|
|
515
|
+
errors.push({
|
|
516
|
+
field: 'description',
|
|
517
|
+
message: `Description must be at least ${this.constraints.minMetaGoalDescriptionLength} character(s)`,
|
|
518
|
+
value: description.length,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
else if (description.length > this.constraints.maxMetaGoalDescriptionLength) {
|
|
522
|
+
errors.push({
|
|
523
|
+
field: 'description',
|
|
524
|
+
message: `Description must be at most ${this.constraints.maxMetaGoalDescriptionLength} characters`,
|
|
525
|
+
value: description.length,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
validateMetaGoalPriority(priority, errors) {
|
|
530
|
+
if (typeof priority !== 'number') {
|
|
531
|
+
errors.push({
|
|
532
|
+
field: 'priority',
|
|
533
|
+
message: 'Priority must be a number',
|
|
534
|
+
value: typeof priority,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
else if (priority < this.constraints.minMetaGoalPriority) {
|
|
538
|
+
errors.push({
|
|
539
|
+
field: 'priority',
|
|
540
|
+
message: `Priority must be at least ${this.constraints.minMetaGoalPriority}`,
|
|
541
|
+
value: priority,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
else if (priority > this.constraints.maxMetaGoalPriority) {
|
|
545
|
+
errors.push({
|
|
546
|
+
field: 'priority',
|
|
547
|
+
message: `Priority must be at most ${this.constraints.maxMetaGoalPriority}`,
|
|
548
|
+
value: priority,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
validateMetaGoalScope(scope, errors) {
|
|
553
|
+
if (scope !== 'all' && !Array.isArray(scope)) {
|
|
554
|
+
errors.push({
|
|
555
|
+
field: 'scope',
|
|
556
|
+
message: 'Scope must be "all" or an array of goal hashes',
|
|
557
|
+
value: scope,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
else if (Array.isArray(scope)) {
|
|
561
|
+
for (let i = 0; i < scope.length; i++) {
|
|
562
|
+
if (!isValidGoalHash(scope[i])) {
|
|
563
|
+
errors.push({
|
|
564
|
+
field: `scope[${i}]`,
|
|
565
|
+
message: 'Invalid goal hash in scope',
|
|
566
|
+
value: scope[i],
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
validateDateString(dateStr, field, errors) {
|
|
573
|
+
if (typeof dateStr !== 'string') {
|
|
574
|
+
errors.push({
|
|
575
|
+
field,
|
|
576
|
+
message: `${field} must be a string`,
|
|
577
|
+
value: typeof dateStr,
|
|
578
|
+
});
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const date = new Date(dateStr);
|
|
582
|
+
if (isNaN(date.getTime())) {
|
|
583
|
+
errors.push({
|
|
584
|
+
field,
|
|
585
|
+
message: `${field} must be a valid date string (ISO 8601 format)`,
|
|
586
|
+
value: dateStr,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
validateDependencies(deps, errors) {
|
|
591
|
+
if (!Array.isArray(deps)) {
|
|
592
|
+
errors.push({
|
|
593
|
+
field: 'dependsOn',
|
|
594
|
+
message: 'Dependencies must be an array of goal hashes',
|
|
595
|
+
value: deps,
|
|
596
|
+
});
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (deps.length > this.constraints.maxDependencies) {
|
|
600
|
+
errors.push({
|
|
601
|
+
field: 'dependsOn',
|
|
602
|
+
message: `Cannot have more than ${this.constraints.maxDependencies} dependencies`,
|
|
603
|
+
value: deps.length,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
for (let i = 0; i < deps.length; i++) {
|
|
607
|
+
if (!isValidGoalHash(deps[i])) {
|
|
608
|
+
errors.push({
|
|
609
|
+
field: `dependsOn[${i}]`,
|
|
610
|
+
message: 'Invalid goal hash in dependencies',
|
|
611
|
+
value: deps[i],
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
issuesToRecord(issues) {
|
|
617
|
+
const record = {};
|
|
618
|
+
for (const issue of issues) {
|
|
619
|
+
if (!record[issue.field]) {
|
|
620
|
+
record[issue.field] = [];
|
|
621
|
+
}
|
|
622
|
+
record[issue.field].push(issue.message);
|
|
623
|
+
}
|
|
624
|
+
return record;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
export function createGoalValidator(constraints = {}) {
|
|
628
|
+
return new GoalValidator(constraints);
|
|
629
|
+
}
|
|
630
|
+
export function validateCreateGoalInput(input) {
|
|
631
|
+
const validator = new GoalValidator();
|
|
632
|
+
validator.validateCreateGoalInput(input);
|
|
633
|
+
}
|
|
634
|
+
export function validateUpdateGoalInput(input) {
|
|
635
|
+
const validator = new GoalValidator();
|
|
636
|
+
validator.validateUpdateGoalInput(input);
|
|
637
|
+
}
|
|
638
|
+
export function validateCreateTaskInput(input) {
|
|
639
|
+
const validator = new GoalValidator();
|
|
640
|
+
validator.validateCreateTaskInput(input);
|
|
641
|
+
}
|
|
642
|
+
export function validateCreateMilestoneInput(input) {
|
|
643
|
+
const validator = new GoalValidator();
|
|
644
|
+
validator.validateCreateMilestoneInput(input);
|
|
645
|
+
}
|
|
646
|
+
export function validateCreateMemoryInput(input) {
|
|
647
|
+
const validator = new GoalValidator();
|
|
648
|
+
validator.validateCreateMemoryInput(input);
|
|
649
|
+
}
|
|
650
|
+
export function validateCreateMetaGoalInput(input) {
|
|
651
|
+
const validator = new GoalValidator();
|
|
652
|
+
validator.validateCreateMetaGoalInput(input);
|
|
653
|
+
}
|
|
654
|
+
export function validateGoalHash(hash) {
|
|
655
|
+
const validator = new GoalValidator();
|
|
656
|
+
validator.validateGoalHash(hash);
|
|
657
|
+
}
|