@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,535 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { GoalApiClient, } from './GoalApiClient.js';
|
|
3
|
+
import { GoalValidator } from './GoalValidator.js';
|
|
4
|
+
import { GoalCache } from './GoalCache.js';
|
|
5
|
+
import { GoalNotFoundError, GoalBlockedError, GoalCircularDependencyError, } from './errors.js';
|
|
6
|
+
const noopLogger = {
|
|
7
|
+
debug: () => { },
|
|
8
|
+
info: () => { },
|
|
9
|
+
warn: () => { },
|
|
10
|
+
error: () => { },
|
|
11
|
+
};
|
|
12
|
+
export class GoalManager extends EventEmitter {
|
|
13
|
+
apiClient;
|
|
14
|
+
validator;
|
|
15
|
+
cache;
|
|
16
|
+
logger;
|
|
17
|
+
apiCallCount = 0;
|
|
18
|
+
loadedGoals = new Set();
|
|
19
|
+
constructor(options) {
|
|
20
|
+
super();
|
|
21
|
+
this.logger = options.logger ?? noopLogger;
|
|
22
|
+
this.apiClient = new GoalApiClient({
|
|
23
|
+
baseUrl: options.baseUrl,
|
|
24
|
+
tokenProvider: options.tokenProvider,
|
|
25
|
+
logger: this.logger,
|
|
26
|
+
...options.apiOptions,
|
|
27
|
+
});
|
|
28
|
+
this.validator = new GoalValidator(options.validationConstraints);
|
|
29
|
+
this.cache = new GoalCache({
|
|
30
|
+
logger: this.logger,
|
|
31
|
+
...options.cacheOptions,
|
|
32
|
+
});
|
|
33
|
+
this.cache.on('cleared', (type) => {
|
|
34
|
+
this.emit('cacheInvalidated', type ?? 'all');
|
|
35
|
+
});
|
|
36
|
+
this.logger.info('GOAL_MANAGER', 'Manager initialized');
|
|
37
|
+
}
|
|
38
|
+
async initialize() {
|
|
39
|
+
this.logger.debug('GOAL_MANAGER', 'Async initialization complete');
|
|
40
|
+
}
|
|
41
|
+
async listGoals(options) {
|
|
42
|
+
const cacheKey = JSON.stringify(options ?? {});
|
|
43
|
+
if (!options?.forceRefresh) {
|
|
44
|
+
const cached = this.cache.getGoalList(cacheKey);
|
|
45
|
+
if (cached) {
|
|
46
|
+
return cached;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
this.apiCallCount++;
|
|
50
|
+
const goals = await this.apiClient.listGoals(options);
|
|
51
|
+
this.cache.setGoalList(goals, cacheKey);
|
|
52
|
+
for (const goal of goals) {
|
|
53
|
+
this.loadedGoals.add(goal.hash);
|
|
54
|
+
}
|
|
55
|
+
return goals;
|
|
56
|
+
}
|
|
57
|
+
async getGoal(hash, forceRefresh = false) {
|
|
58
|
+
this.validator.validateGoalHash(hash);
|
|
59
|
+
if (!forceRefresh) {
|
|
60
|
+
const cached = this.cache.getGoal(hash);
|
|
61
|
+
if (cached) {
|
|
62
|
+
return cached;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
this.apiCallCount++;
|
|
66
|
+
const goal = await this.apiClient.getGoal(hash);
|
|
67
|
+
this.cache.setGoal(goal);
|
|
68
|
+
this.loadedGoals.add(goal.hash);
|
|
69
|
+
return goal;
|
|
70
|
+
}
|
|
71
|
+
async createGoal(input) {
|
|
72
|
+
this.validator.validateCreateGoalInput(input);
|
|
73
|
+
if (input.dependsOn?.length) {
|
|
74
|
+
await this.validateNoDependencyCycle(input.dependsOn, input.parentHash);
|
|
75
|
+
}
|
|
76
|
+
this.apiCallCount++;
|
|
77
|
+
const goal = await this.apiClient.createGoal(input);
|
|
78
|
+
this.cache.setGoal(goal);
|
|
79
|
+
this.cache.invalidateAllGoals();
|
|
80
|
+
this.loadedGoals.add(goal.hash);
|
|
81
|
+
this.emit('goalCreated', goal);
|
|
82
|
+
this.logger.info('GOAL_MANAGER', 'Goal created', { hash: goal.hash, name: goal.name });
|
|
83
|
+
return goal;
|
|
84
|
+
}
|
|
85
|
+
async updateGoal(hash, input) {
|
|
86
|
+
this.validator.validateGoalHash(hash);
|
|
87
|
+
this.validator.validateUpdateGoalInput(input);
|
|
88
|
+
if (input.dependsOn?.length) {
|
|
89
|
+
await this.validateNoDependencyCycle(input.dependsOn, undefined, hash);
|
|
90
|
+
}
|
|
91
|
+
this.apiCallCount++;
|
|
92
|
+
const goal = await this.apiClient.updateGoal(hash, input);
|
|
93
|
+
this.cache.setGoal(goal);
|
|
94
|
+
this.cache.invalidateAllGoals();
|
|
95
|
+
this.emit('goalUpdated', goal);
|
|
96
|
+
this.logger.info('GOAL_MANAGER', 'Goal updated', { hash: goal.hash });
|
|
97
|
+
return goal;
|
|
98
|
+
}
|
|
99
|
+
async deleteGoal(hash) {
|
|
100
|
+
this.validator.validateGoalHash(hash);
|
|
101
|
+
this.apiCallCount++;
|
|
102
|
+
await this.apiClient.deleteGoal(hash);
|
|
103
|
+
this.cache.invalidateGoal(hash);
|
|
104
|
+
this.cache.invalidateMilestones(hash);
|
|
105
|
+
this.cache.invalidateTasks(hash);
|
|
106
|
+
this.cache.invalidateMemories(hash);
|
|
107
|
+
this.loadedGoals.delete(hash);
|
|
108
|
+
this.emit('goalDeleted', hash);
|
|
109
|
+
this.logger.info('GOAL_MANAGER', 'Goal deleted', { hash });
|
|
110
|
+
}
|
|
111
|
+
async pauseGoal(hash) {
|
|
112
|
+
this.validator.validateGoalHash(hash);
|
|
113
|
+
this.apiCallCount++;
|
|
114
|
+
const goal = await this.apiClient.pauseGoal(hash);
|
|
115
|
+
this.cache.setGoal(goal);
|
|
116
|
+
this.emit('goalPaused', goal);
|
|
117
|
+
this.logger.info('GOAL_MANAGER', 'Goal paused', { hash });
|
|
118
|
+
return goal;
|
|
119
|
+
}
|
|
120
|
+
async resumeGoal(hash) {
|
|
121
|
+
this.validator.validateGoalHash(hash);
|
|
122
|
+
const goal = await this.getGoal(hash);
|
|
123
|
+
if (goal.isBlocked && goal.blockedBy?.length) {
|
|
124
|
+
throw new GoalBlockedError(hash, goal.blockedBy);
|
|
125
|
+
}
|
|
126
|
+
this.apiCallCount++;
|
|
127
|
+
const updated = await this.apiClient.resumeGoal(hash);
|
|
128
|
+
this.cache.setGoal(updated);
|
|
129
|
+
this.emit('goalResumed', updated);
|
|
130
|
+
this.logger.info('GOAL_MANAGER', 'Goal resumed', { hash });
|
|
131
|
+
return updated;
|
|
132
|
+
}
|
|
133
|
+
async completeGoal(hash) {
|
|
134
|
+
this.validator.validateGoalHash(hash);
|
|
135
|
+
this.apiCallCount++;
|
|
136
|
+
const goal = await this.apiClient.completeGoal(hash);
|
|
137
|
+
this.cache.setGoal(goal);
|
|
138
|
+
this.cache.invalidateAllGoals();
|
|
139
|
+
this.emit('goalCompleted', goal);
|
|
140
|
+
this.logger.info('GOAL_MANAGER', 'Goal completed', { hash });
|
|
141
|
+
return goal;
|
|
142
|
+
}
|
|
143
|
+
async abandonGoal(hash) {
|
|
144
|
+
this.validator.validateGoalHash(hash);
|
|
145
|
+
this.apiCallCount++;
|
|
146
|
+
const goal = await this.apiClient.abandonGoal(hash);
|
|
147
|
+
this.cache.setGoal(goal);
|
|
148
|
+
this.emit('goalAbandoned', goal);
|
|
149
|
+
this.logger.info('GOAL_MANAGER', 'Goal abandoned', { hash });
|
|
150
|
+
return goal;
|
|
151
|
+
}
|
|
152
|
+
async getGoalProgress(hash) {
|
|
153
|
+
this.validator.validateGoalHash(hash);
|
|
154
|
+
this.apiCallCount++;
|
|
155
|
+
return await this.apiClient.getGoalProgress(hash);
|
|
156
|
+
}
|
|
157
|
+
async listMilestones(goalHash, forceRefresh = false) {
|
|
158
|
+
this.validator.validateGoalHash(goalHash);
|
|
159
|
+
if (!forceRefresh) {
|
|
160
|
+
const cached = this.cache.getMilestones(goalHash);
|
|
161
|
+
if (cached) {
|
|
162
|
+
return cached;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
this.apiCallCount++;
|
|
166
|
+
const milestones = await this.apiClient.listMilestones(goalHash);
|
|
167
|
+
this.cache.setMilestones(goalHash, milestones);
|
|
168
|
+
return milestones;
|
|
169
|
+
}
|
|
170
|
+
async createMilestone(goalHash, input) {
|
|
171
|
+
this.validator.validateGoalHash(goalHash);
|
|
172
|
+
this.validator.validateCreateMilestoneInput(input);
|
|
173
|
+
this.apiCallCount++;
|
|
174
|
+
const milestone = await this.apiClient.createMilestone(goalHash, input);
|
|
175
|
+
this.cache.invalidateMilestones(goalHash);
|
|
176
|
+
this.emit('milestoneCreated', goalHash, milestone);
|
|
177
|
+
this.logger.info('GOAL_MANAGER', 'Milestone created', { goalHash, id: milestone.id });
|
|
178
|
+
return milestone;
|
|
179
|
+
}
|
|
180
|
+
async getMilestone(goalHash, milestoneId) {
|
|
181
|
+
this.validator.validateGoalHash(goalHash);
|
|
182
|
+
this.apiCallCount++;
|
|
183
|
+
return await this.apiClient.getMilestone(goalHash, milestoneId);
|
|
184
|
+
}
|
|
185
|
+
async updateMilestone(goalHash, milestoneId, input) {
|
|
186
|
+
this.validator.validateGoalHash(goalHash);
|
|
187
|
+
this.validator.validateUpdateMilestoneInput(input);
|
|
188
|
+
this.apiCallCount++;
|
|
189
|
+
const milestone = await this.apiClient.updateMilestone(goalHash, milestoneId, input);
|
|
190
|
+
this.cache.invalidateMilestones(goalHash);
|
|
191
|
+
this.emit('milestoneUpdated', goalHash, milestone);
|
|
192
|
+
this.logger.info('GOAL_MANAGER', 'Milestone updated', { goalHash, id: milestoneId });
|
|
193
|
+
return milestone;
|
|
194
|
+
}
|
|
195
|
+
async deleteMilestone(goalHash, milestoneId) {
|
|
196
|
+
this.validator.validateGoalHash(goalHash);
|
|
197
|
+
this.apiCallCount++;
|
|
198
|
+
await this.apiClient.deleteMilestone(goalHash, milestoneId);
|
|
199
|
+
this.cache.invalidateMilestones(goalHash);
|
|
200
|
+
this.emit('milestoneDeleted', goalHash, milestoneId);
|
|
201
|
+
this.logger.info('GOAL_MANAGER', 'Milestone deleted', { goalHash, id: milestoneId });
|
|
202
|
+
}
|
|
203
|
+
async completeMilestone(goalHash, milestoneId) {
|
|
204
|
+
this.validator.validateGoalHash(goalHash);
|
|
205
|
+
this.apiCallCount++;
|
|
206
|
+
const milestone = await this.apiClient.completeMilestone(goalHash, milestoneId);
|
|
207
|
+
this.cache.invalidateMilestones(goalHash);
|
|
208
|
+
this.cache.invalidateGoal(goalHash);
|
|
209
|
+
this.emit('milestoneCompleted', goalHash, milestone);
|
|
210
|
+
this.logger.info('GOAL_MANAGER', 'Milestone completed', { goalHash, id: milestoneId });
|
|
211
|
+
return milestone;
|
|
212
|
+
}
|
|
213
|
+
async listTasks(goalHash, options) {
|
|
214
|
+
this.validator.validateGoalHash(goalHash);
|
|
215
|
+
if (!options?.forceRefresh) {
|
|
216
|
+
const cached = this.cache.getTasks(goalHash);
|
|
217
|
+
if (cached) {
|
|
218
|
+
return cached;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
this.apiCallCount++;
|
|
222
|
+
const tasks = await this.apiClient.listTasks(goalHash, options);
|
|
223
|
+
this.cache.setTasks(goalHash, tasks);
|
|
224
|
+
return tasks;
|
|
225
|
+
}
|
|
226
|
+
async getPendingTasks() {
|
|
227
|
+
this.apiCallCount++;
|
|
228
|
+
return await this.apiClient.getPendingTasks();
|
|
229
|
+
}
|
|
230
|
+
async getTasksAwaitingApproval() {
|
|
231
|
+
this.apiCallCount++;
|
|
232
|
+
return await this.apiClient.getTasksAwaitingApproval();
|
|
233
|
+
}
|
|
234
|
+
async createTask(goalHash, input) {
|
|
235
|
+
this.validator.validateGoalHash(goalHash);
|
|
236
|
+
this.validator.validateCreateTaskInput(input);
|
|
237
|
+
this.apiCallCount++;
|
|
238
|
+
const task = await this.apiClient.createTask(goalHash, input);
|
|
239
|
+
this.cache.invalidateTasks(goalHash);
|
|
240
|
+
this.emit('taskCreated', goalHash, task);
|
|
241
|
+
this.logger.info('GOAL_MANAGER', 'Task created', { goalHash, id: task.id });
|
|
242
|
+
return task;
|
|
243
|
+
}
|
|
244
|
+
async getTask(goalHash, taskId) {
|
|
245
|
+
this.validator.validateGoalHash(goalHash);
|
|
246
|
+
this.apiCallCount++;
|
|
247
|
+
return await this.apiClient.getTask(goalHash, taskId);
|
|
248
|
+
}
|
|
249
|
+
async updateTask(goalHash, taskId, input) {
|
|
250
|
+
this.validator.validateGoalHash(goalHash);
|
|
251
|
+
this.validator.validateUpdateTaskInput(input);
|
|
252
|
+
this.apiCallCount++;
|
|
253
|
+
const task = await this.apiClient.updateTask(goalHash, taskId, input);
|
|
254
|
+
this.cache.invalidateTasks(goalHash);
|
|
255
|
+
this.emit('taskUpdated', goalHash, task);
|
|
256
|
+
this.logger.info('GOAL_MANAGER', 'Task updated', { goalHash, id: taskId });
|
|
257
|
+
return task;
|
|
258
|
+
}
|
|
259
|
+
async deleteTask(goalHash, taskId) {
|
|
260
|
+
this.validator.validateGoalHash(goalHash);
|
|
261
|
+
this.apiCallCount++;
|
|
262
|
+
await this.apiClient.deleteTask(goalHash, taskId);
|
|
263
|
+
this.cache.invalidateTasks(goalHash);
|
|
264
|
+
this.emit('taskDeleted', goalHash, taskId);
|
|
265
|
+
this.logger.info('GOAL_MANAGER', 'Task deleted', { goalHash, id: taskId });
|
|
266
|
+
}
|
|
267
|
+
async approveTask(goalHash, taskId) {
|
|
268
|
+
this.validator.validateGoalHash(goalHash);
|
|
269
|
+
const currentTask = await this.getTask(goalHash, taskId);
|
|
270
|
+
const previousStatus = currentTask.status;
|
|
271
|
+
this.apiCallCount++;
|
|
272
|
+
const task = await this.apiClient.approveTask(goalHash, taskId);
|
|
273
|
+
this.cache.invalidateTasks(goalHash);
|
|
274
|
+
this.emit('taskStatusChanged', goalHash, task, previousStatus);
|
|
275
|
+
this.logger.info('GOAL_MANAGER', 'Task approved', { goalHash, id: taskId });
|
|
276
|
+
return task;
|
|
277
|
+
}
|
|
278
|
+
async denyTask(goalHash, taskId) {
|
|
279
|
+
this.validator.validateGoalHash(goalHash);
|
|
280
|
+
const currentTask = await this.getTask(goalHash, taskId);
|
|
281
|
+
const previousStatus = currentTask.status;
|
|
282
|
+
this.apiCallCount++;
|
|
283
|
+
const task = await this.apiClient.denyTask(goalHash, taskId);
|
|
284
|
+
this.cache.invalidateTasks(goalHash);
|
|
285
|
+
this.emit('taskStatusChanged', goalHash, task, previousStatus);
|
|
286
|
+
this.logger.info('GOAL_MANAGER', 'Task denied', { goalHash, id: taskId });
|
|
287
|
+
return task;
|
|
288
|
+
}
|
|
289
|
+
async startTask(goalHash, taskId) {
|
|
290
|
+
this.validator.validateGoalHash(goalHash);
|
|
291
|
+
const currentTask = await this.getTask(goalHash, taskId);
|
|
292
|
+
const previousStatus = currentTask.status;
|
|
293
|
+
this.apiCallCount++;
|
|
294
|
+
const task = await this.apiClient.startTask(goalHash, taskId);
|
|
295
|
+
this.cache.invalidateTasks(goalHash);
|
|
296
|
+
this.emit('taskStatusChanged', goalHash, task, previousStatus);
|
|
297
|
+
this.logger.info('GOAL_MANAGER', 'Task started', { goalHash, id: taskId });
|
|
298
|
+
return task;
|
|
299
|
+
}
|
|
300
|
+
async completeTask(goalHash, taskId, result) {
|
|
301
|
+
this.validator.validateGoalHash(goalHash);
|
|
302
|
+
const currentTask = await this.getTask(goalHash, taskId);
|
|
303
|
+
const previousStatus = currentTask.status;
|
|
304
|
+
this.apiCallCount++;
|
|
305
|
+
const task = await this.apiClient.completeTask(goalHash, taskId, result);
|
|
306
|
+
this.cache.invalidateTasks(goalHash);
|
|
307
|
+
this.cache.invalidateGoal(goalHash);
|
|
308
|
+
this.emit('taskStatusChanged', goalHash, task, previousStatus);
|
|
309
|
+
this.logger.info('GOAL_MANAGER', 'Task completed', { goalHash, id: taskId });
|
|
310
|
+
return task;
|
|
311
|
+
}
|
|
312
|
+
async failTask(goalHash, taskId, input) {
|
|
313
|
+
this.validator.validateGoalHash(goalHash);
|
|
314
|
+
const currentTask = await this.getTask(goalHash, taskId);
|
|
315
|
+
const previousStatus = currentTask.status;
|
|
316
|
+
this.apiCallCount++;
|
|
317
|
+
const task = await this.apiClient.failTask(goalHash, taskId, input);
|
|
318
|
+
this.cache.invalidateTasks(goalHash);
|
|
319
|
+
this.emit('taskStatusChanged', goalHash, task, previousStatus);
|
|
320
|
+
this.logger.info('GOAL_MANAGER', 'Task failed', { goalHash, id: taskId, error: input.error });
|
|
321
|
+
return task;
|
|
322
|
+
}
|
|
323
|
+
async skipTask(goalHash, taskId) {
|
|
324
|
+
this.validator.validateGoalHash(goalHash);
|
|
325
|
+
const currentTask = await this.getTask(goalHash, taskId);
|
|
326
|
+
const previousStatus = currentTask.status;
|
|
327
|
+
this.apiCallCount++;
|
|
328
|
+
const task = await this.apiClient.skipTask(goalHash, taskId);
|
|
329
|
+
this.cache.invalidateTasks(goalHash);
|
|
330
|
+
this.emit('taskStatusChanged', goalHash, task, previousStatus);
|
|
331
|
+
this.logger.info('GOAL_MANAGER', 'Task skipped', { goalHash, id: taskId });
|
|
332
|
+
return task;
|
|
333
|
+
}
|
|
334
|
+
async addTaskFeedback(goalHash, taskId, input) {
|
|
335
|
+
this.validator.validateGoalHash(goalHash);
|
|
336
|
+
this.validator.validateTaskFeedbackInput(input);
|
|
337
|
+
this.apiCallCount++;
|
|
338
|
+
const task = await this.apiClient.addTaskFeedback(goalHash, taskId, input);
|
|
339
|
+
this.cache.invalidateTasks(goalHash);
|
|
340
|
+
this.emit('taskUpdated', goalHash, task);
|
|
341
|
+
this.logger.info('GOAL_MANAGER', 'Task feedback added', { goalHash, id: taskId, quality: input.quality });
|
|
342
|
+
return task;
|
|
343
|
+
}
|
|
344
|
+
async listMemories(goalHash, forceRefresh = false) {
|
|
345
|
+
this.validator.validateGoalHash(goalHash);
|
|
346
|
+
if (!forceRefresh) {
|
|
347
|
+
const cached = this.cache.getMemories(goalHash);
|
|
348
|
+
if (cached) {
|
|
349
|
+
return cached;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
this.apiCallCount++;
|
|
353
|
+
const memories = await this.apiClient.listMemories(goalHash);
|
|
354
|
+
this.cache.setMemories(goalHash, memories);
|
|
355
|
+
return memories;
|
|
356
|
+
}
|
|
357
|
+
async getEnabledMemories(goalHash) {
|
|
358
|
+
this.validator.validateGoalHash(goalHash);
|
|
359
|
+
this.apiCallCount++;
|
|
360
|
+
return await this.apiClient.getEnabledMemories(goalHash);
|
|
361
|
+
}
|
|
362
|
+
async createMemory(goalHash, input) {
|
|
363
|
+
this.validator.validateGoalHash(goalHash);
|
|
364
|
+
this.validator.validateCreateMemoryInput(input);
|
|
365
|
+
this.apiCallCount++;
|
|
366
|
+
const memory = await this.apiClient.createMemory(goalHash, input);
|
|
367
|
+
this.cache.invalidateMemories(goalHash);
|
|
368
|
+
this.emit('memoryCreated', goalHash, memory);
|
|
369
|
+
this.logger.info('GOAL_MANAGER', 'Memory created', { goalHash, id: memory.id });
|
|
370
|
+
return memory;
|
|
371
|
+
}
|
|
372
|
+
async updateMemory(goalHash, memoryId, input) {
|
|
373
|
+
this.validator.validateGoalHash(goalHash);
|
|
374
|
+
this.validator.validateUpdateMemoryInput(input);
|
|
375
|
+
this.apiCallCount++;
|
|
376
|
+
const memory = await this.apiClient.updateMemory(goalHash, memoryId, input);
|
|
377
|
+
this.cache.invalidateMemories(goalHash);
|
|
378
|
+
this.emit('memoryUpdated', goalHash, memory);
|
|
379
|
+
this.logger.info('GOAL_MANAGER', 'Memory updated', { goalHash, id: memoryId });
|
|
380
|
+
return memory;
|
|
381
|
+
}
|
|
382
|
+
async toggleMemory(goalHash, memoryId) {
|
|
383
|
+
this.validator.validateGoalHash(goalHash);
|
|
384
|
+
this.apiCallCount++;
|
|
385
|
+
const memory = await this.apiClient.toggleMemory(goalHash, memoryId);
|
|
386
|
+
this.cache.invalidateMemories(goalHash);
|
|
387
|
+
this.emit('memoryUpdated', goalHash, memory);
|
|
388
|
+
this.logger.info('GOAL_MANAGER', 'Memory toggled', { goalHash, id: memoryId, enabled: memory.isEnabled });
|
|
389
|
+
return memory;
|
|
390
|
+
}
|
|
391
|
+
async deleteMemory(goalHash, memoryId) {
|
|
392
|
+
this.validator.validateGoalHash(goalHash);
|
|
393
|
+
this.apiCallCount++;
|
|
394
|
+
await this.apiClient.deleteMemory(goalHash, memoryId);
|
|
395
|
+
this.cache.invalidateMemories(goalHash);
|
|
396
|
+
this.emit('memoryDeleted', goalHash, memoryId);
|
|
397
|
+
this.logger.info('GOAL_MANAGER', 'Memory deleted', { goalHash, id: memoryId });
|
|
398
|
+
}
|
|
399
|
+
async listMetaGoals(active, forceRefresh = false) {
|
|
400
|
+
const cacheKey = active !== undefined ? `active:${active}` : 'all';
|
|
401
|
+
if (!forceRefresh) {
|
|
402
|
+
const cached = this.cache.getMetaGoals(cacheKey);
|
|
403
|
+
if (cached) {
|
|
404
|
+
return cached;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
this.apiCallCount++;
|
|
408
|
+
const metaGoals = await this.apiClient.listMetaGoals(active);
|
|
409
|
+
this.cache.setMetaGoals(metaGoals, cacheKey);
|
|
410
|
+
return metaGoals;
|
|
411
|
+
}
|
|
412
|
+
async getMetaGoalsForGoal(goalHash) {
|
|
413
|
+
this.validator.validateGoalHash(goalHash);
|
|
414
|
+
this.apiCallCount++;
|
|
415
|
+
return await this.apiClient.getMetaGoalsForGoal(goalHash);
|
|
416
|
+
}
|
|
417
|
+
async createMetaGoal(input) {
|
|
418
|
+
this.validator.validateCreateMetaGoalInput(input);
|
|
419
|
+
this.apiCallCount++;
|
|
420
|
+
const metaGoal = await this.apiClient.createMetaGoal(input);
|
|
421
|
+
this.cache.invalidateMetaGoals();
|
|
422
|
+
this.emit('metaGoalCreated', metaGoal);
|
|
423
|
+
this.logger.info('GOAL_MANAGER', 'Meta-goal created', { id: metaGoal.id });
|
|
424
|
+
return metaGoal;
|
|
425
|
+
}
|
|
426
|
+
async getMetaGoal(id) {
|
|
427
|
+
this.apiCallCount++;
|
|
428
|
+
return await this.apiClient.getMetaGoal(id);
|
|
429
|
+
}
|
|
430
|
+
async updateMetaGoal(id, input) {
|
|
431
|
+
this.validator.validateUpdateMetaGoalInput(input);
|
|
432
|
+
this.apiCallCount++;
|
|
433
|
+
const metaGoal = await this.apiClient.updateMetaGoal(id, input);
|
|
434
|
+
this.cache.invalidateMetaGoals();
|
|
435
|
+
this.emit('metaGoalUpdated', metaGoal);
|
|
436
|
+
this.logger.info('GOAL_MANAGER', 'Meta-goal updated', { id });
|
|
437
|
+
return metaGoal;
|
|
438
|
+
}
|
|
439
|
+
async toggleMetaGoal(id) {
|
|
440
|
+
this.apiCallCount++;
|
|
441
|
+
const metaGoal = await this.apiClient.toggleMetaGoal(id);
|
|
442
|
+
this.cache.invalidateMetaGoals();
|
|
443
|
+
this.emit('metaGoalToggled', metaGoal);
|
|
444
|
+
this.logger.info('GOAL_MANAGER', 'Meta-goal toggled', { id, active: metaGoal.isActive });
|
|
445
|
+
return metaGoal;
|
|
446
|
+
}
|
|
447
|
+
async deleteMetaGoal(id) {
|
|
448
|
+
this.apiCallCount++;
|
|
449
|
+
await this.apiClient.deleteMetaGoal(id);
|
|
450
|
+
this.cache.invalidateMetaGoals();
|
|
451
|
+
this.emit('metaGoalDeleted', id);
|
|
452
|
+
this.logger.info('GOAL_MANAGER', 'Meta-goal deleted', { id });
|
|
453
|
+
}
|
|
454
|
+
async getActionLogs(goalHash, limit) {
|
|
455
|
+
this.validator.validateGoalHash(goalHash);
|
|
456
|
+
this.apiCallCount++;
|
|
457
|
+
return await this.apiClient.getActionLogs(goalHash, limit);
|
|
458
|
+
}
|
|
459
|
+
clearCache() {
|
|
460
|
+
this.cache.clearAll();
|
|
461
|
+
this.logger.info('GOAL_MANAGER', 'Cache cleared');
|
|
462
|
+
}
|
|
463
|
+
pruneCache() {
|
|
464
|
+
return this.cache.pruneExpired();
|
|
465
|
+
}
|
|
466
|
+
getCacheStats() {
|
|
467
|
+
return this.cache.getStats();
|
|
468
|
+
}
|
|
469
|
+
getStats() {
|
|
470
|
+
return {
|
|
471
|
+
cache: this.cache.getStats(),
|
|
472
|
+
apiCalls: this.apiCallCount,
|
|
473
|
+
goalsLoaded: this.loadedGoals.size,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
resetStats() {
|
|
477
|
+
this.apiCallCount = 0;
|
|
478
|
+
this.loadedGoals.clear();
|
|
479
|
+
this.cache.resetStats();
|
|
480
|
+
this.logger.debug('GOAL_MANAGER', 'Statistics reset');
|
|
481
|
+
}
|
|
482
|
+
async validateNoDependencyCycle(newDeps, parentHash, currentHash) {
|
|
483
|
+
const goals = await this.listGoals();
|
|
484
|
+
const goalMap = new Map(goals.map((g) => [g.hash, g]));
|
|
485
|
+
for (const depHash of newDeps) {
|
|
486
|
+
if (depHash === currentHash) {
|
|
487
|
+
throw new GoalCircularDependencyError(currentHash, [currentHash]);
|
|
488
|
+
}
|
|
489
|
+
if (!goalMap.has(depHash)) {
|
|
490
|
+
throw new GoalNotFoundError(depHash);
|
|
491
|
+
}
|
|
492
|
+
const visited = new Set();
|
|
493
|
+
const path = [];
|
|
494
|
+
if (currentHash) {
|
|
495
|
+
visited.add(currentHash);
|
|
496
|
+
path.push(currentHash);
|
|
497
|
+
}
|
|
498
|
+
if (this.hasCycleDFS(depHash, currentHash ?? '', goalMap, visited, path)) {
|
|
499
|
+
throw new GoalCircularDependencyError(currentHash ?? depHash, path);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (parentHash) {
|
|
503
|
+
if (parentHash === currentHash) {
|
|
504
|
+
throw new GoalCircularDependencyError(currentHash, [currentHash]);
|
|
505
|
+
}
|
|
506
|
+
if (!goalMap.has(parentHash)) {
|
|
507
|
+
throw new GoalNotFoundError(parentHash);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
hasCycleDFS(hash, targetHash, goalMap, visited, path) {
|
|
512
|
+
if (hash === targetHash && visited.has(hash)) {
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
if (visited.has(hash)) {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
visited.add(hash);
|
|
519
|
+
path.push(hash);
|
|
520
|
+
const goal = goalMap.get(hash);
|
|
521
|
+
if (!goal) {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
for (const depHash of goal.dependsOn) {
|
|
525
|
+
if (this.hasCycleDFS(depHash, targetHash, goalMap, visited, path)) {
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
path.pop();
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
export function createGoalManager(options) {
|
|
534
|
+
return new GoalManager(options);
|
|
535
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { Goal, GoalHash, Milestone, Task, Logger } from './types.js';
|
|
3
|
+
import type { GoalProgress, ProgressSnapshot, TaskStats, VelocityMetrics } from './ProgressTracker.js';
|
|
4
|
+
export type ReportFormat = 'text' | 'markdown' | 'json' | 'html';
|
|
5
|
+
export type ReportDetailLevel = 'summary' | 'standard' | 'detailed' | 'full';
|
|
6
|
+
export type ReportSection = 'overview' | 'milestones' | 'tasks' | 'velocity' | 'history' | 'actions' | 'risks';
|
|
7
|
+
export interface ProgressReport {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly goalHash: GoalHash;
|
|
10
|
+
readonly title: string;
|
|
11
|
+
readonly generatedAt: Date;
|
|
12
|
+
readonly period: ReportPeriod;
|
|
13
|
+
readonly format: ReportFormat;
|
|
14
|
+
readonly detailLevel: ReportDetailLevel;
|
|
15
|
+
readonly content: string;
|
|
16
|
+
readonly sections: ReportSectionContent[];
|
|
17
|
+
readonly metadata: ReportMetadata;
|
|
18
|
+
}
|
|
19
|
+
export interface ReportPeriod {
|
|
20
|
+
readonly start: Date;
|
|
21
|
+
readonly end: Date;
|
|
22
|
+
readonly label: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ReportSectionContent {
|
|
25
|
+
readonly section: ReportSection;
|
|
26
|
+
readonly title: string;
|
|
27
|
+
readonly content: string;
|
|
28
|
+
readonly data?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export interface ReportMetadata {
|
|
31
|
+
readonly progress: number;
|
|
32
|
+
readonly taskStats: TaskStats;
|
|
33
|
+
readonly velocity: VelocityMetrics;
|
|
34
|
+
readonly estimatedCompletion: Date | null;
|
|
35
|
+
readonly previousProgress?: number;
|
|
36
|
+
readonly progressDelta?: number;
|
|
37
|
+
}
|
|
38
|
+
export interface ReportOptions {
|
|
39
|
+
readonly format?: ReportFormat;
|
|
40
|
+
readonly detailLevel?: ReportDetailLevel;
|
|
41
|
+
readonly sections?: ReportSection[];
|
|
42
|
+
readonly periodDays?: number;
|
|
43
|
+
readonly includeHistory?: boolean;
|
|
44
|
+
readonly includeActionItems?: boolean;
|
|
45
|
+
readonly includeRisks?: boolean;
|
|
46
|
+
readonly compareWithSnapshot?: ProgressSnapshot;
|
|
47
|
+
}
|
|
48
|
+
export interface ActionItem {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly type: 'blocked_task' | 'overdue' | 'at_risk' | 'review_needed' | 'approval_pending';
|
|
51
|
+
readonly priority: 'high' | 'medium' | 'low';
|
|
52
|
+
readonly description: string;
|
|
53
|
+
readonly relatedTaskId?: number;
|
|
54
|
+
readonly relatedMilestoneId?: number;
|
|
55
|
+
readonly suggestedAction?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface RiskAssessment {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly severity: 'high' | 'medium' | 'low';
|
|
60
|
+
readonly category: 'deadline' | 'resources' | 'dependencies' | 'technical' | 'other';
|
|
61
|
+
readonly description: string;
|
|
62
|
+
readonly mitigation?: string;
|
|
63
|
+
readonly likelihood: number;
|
|
64
|
+
}
|
|
65
|
+
export interface GoalReporterOptions {
|
|
66
|
+
readonly logger?: Logger;
|
|
67
|
+
readonly defaultFormat?: ReportFormat;
|
|
68
|
+
readonly defaultDetailLevel?: ReportDetailLevel;
|
|
69
|
+
readonly defaultPeriodDays?: number;
|
|
70
|
+
}
|
|
71
|
+
export interface GoalReporterEvents {
|
|
72
|
+
'report:generated': [report: ProgressReport];
|
|
73
|
+
'actions:identified': [goalHash: GoalHash, actions: ActionItem[]];
|
|
74
|
+
'risks:identified': [goalHash: GoalHash, risks: RiskAssessment[]];
|
|
75
|
+
error: [error: Error];
|
|
76
|
+
}
|
|
77
|
+
export declare class GoalReporter extends EventEmitter<GoalReporterEvents> {
|
|
78
|
+
private readonly logger;
|
|
79
|
+
private readonly defaultFormat;
|
|
80
|
+
private readonly defaultDetailLevel;
|
|
81
|
+
private readonly defaultPeriodDays;
|
|
82
|
+
private reportCounter;
|
|
83
|
+
constructor(options?: GoalReporterOptions);
|
|
84
|
+
generateReport(goal: Goal, progress: GoalProgress, milestones: Milestone[], tasks: Task[], snapshots?: ProgressSnapshot[], options?: ReportOptions): ProgressReport;
|
|
85
|
+
generateSummary(goal: Goal, progress: GoalProgress): string;
|
|
86
|
+
generateComparisonReport(goal: Goal, current: GoalProgress, previous: ProgressSnapshot, format?: ReportFormat): string;
|
|
87
|
+
identifyActionItems(goal: Goal, progress: GoalProgress, milestones: Milestone[], tasks: Task[]): ActionItem[];
|
|
88
|
+
assessRisks(goal: Goal, progress: GoalProgress, _milestones: Milestone[], _tasks: Task[]): RiskAssessment[];
|
|
89
|
+
private calculatePeriod;
|
|
90
|
+
private buildMetadata;
|
|
91
|
+
private generateSection;
|
|
92
|
+
private generateOverviewSection;
|
|
93
|
+
private generateMilestonesSection;
|
|
94
|
+
private generateTasksSection;
|
|
95
|
+
private generateVelocitySection;
|
|
96
|
+
private generateHistorySection;
|
|
97
|
+
private generateActionsSection;
|
|
98
|
+
private generateRisksSection;
|
|
99
|
+
private combineContent;
|
|
100
|
+
private renderProgressBar;
|
|
101
|
+
private formatDate;
|
|
102
|
+
private formatDuration;
|
|
103
|
+
private formatTrend;
|
|
104
|
+
}
|
|
105
|
+
export declare function createGoalReporter(options?: GoalReporterOptions): GoalReporter;
|