@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,324 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
const DEFAULT_SNAPSHOT_INTERVAL = 24 * 60 * 60 * 1000;
|
|
3
|
+
const DEFAULT_VELOCITY_WINDOW = 7;
|
|
4
|
+
const DEFAULT_MAX_SNAPSHOTS = 30;
|
|
5
|
+
export class ProgressTracker extends EventEmitter {
|
|
6
|
+
logger;
|
|
7
|
+
snapshotInterval;
|
|
8
|
+
velocityWindow;
|
|
9
|
+
maxSnapshots;
|
|
10
|
+
enableAutoSnapshots;
|
|
11
|
+
completionRecords = new Map();
|
|
12
|
+
snapshots = new Map();
|
|
13
|
+
progressCache = new Map();
|
|
14
|
+
snapshotTimer = null;
|
|
15
|
+
snapshotCounter = 0;
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
super();
|
|
18
|
+
this.logger = options.logger ?? console;
|
|
19
|
+
this.snapshotInterval = options.snapshotInterval ?? DEFAULT_SNAPSHOT_INTERVAL;
|
|
20
|
+
this.velocityWindow = options.velocityWindow ?? DEFAULT_VELOCITY_WINDOW;
|
|
21
|
+
this.maxSnapshots = options.maxSnapshots ?? DEFAULT_MAX_SNAPSHOTS;
|
|
22
|
+
this.enableAutoSnapshots = options.enableAutoSnapshots ?? false;
|
|
23
|
+
if (this.enableAutoSnapshots) {
|
|
24
|
+
this.startAutoSnapshots();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
calculateProgress(goal, milestones, tasks) {
|
|
28
|
+
const goalHash = goal.hash;
|
|
29
|
+
const taskStats = this.calculateTaskStats(tasks);
|
|
30
|
+
const milestoneProgress = this.calculateMilestoneProgress(milestones, tasks);
|
|
31
|
+
const overallProgress = this.calculateOverallProgress(taskStats, milestoneProgress);
|
|
32
|
+
const velocity = this.calculateVelocity(goalHash);
|
|
33
|
+
const estimatedCompletion = this.estimateCompletion(taskStats, velocity);
|
|
34
|
+
const progress = {
|
|
35
|
+
goalHash,
|
|
36
|
+
title: goal.name,
|
|
37
|
+
status: goal.status,
|
|
38
|
+
overallProgress,
|
|
39
|
+
milestoneProgress,
|
|
40
|
+
taskStats,
|
|
41
|
+
velocity,
|
|
42
|
+
estimatedCompletion,
|
|
43
|
+
lastUpdated: new Date(),
|
|
44
|
+
};
|
|
45
|
+
const previousProgress = this.progressCache.get(goalHash);
|
|
46
|
+
this.progressCache.set(goalHash, progress);
|
|
47
|
+
this.emit('progress:updated', goalHash, progress);
|
|
48
|
+
for (const mp of milestoneProgress) {
|
|
49
|
+
if (mp.progress === 100) {
|
|
50
|
+
const previousMp = previousProgress?.milestoneProgress.find((m) => m.milestoneId === mp.milestoneId);
|
|
51
|
+
if (!previousMp || previousMp.progress < 100) {
|
|
52
|
+
this.emit('milestone:completed', goalHash, mp.milestoneId);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (overallProgress === 100 && previousProgress?.overallProgress !== 100) {
|
|
57
|
+
this.emit('goal:completed', goalHash);
|
|
58
|
+
}
|
|
59
|
+
if (estimatedCompletion?.getTime() !==
|
|
60
|
+
previousProgress?.estimatedCompletion?.getTime()) {
|
|
61
|
+
this.emit('estimate:updated', goalHash, estimatedCompletion);
|
|
62
|
+
}
|
|
63
|
+
return progress;
|
|
64
|
+
}
|
|
65
|
+
recordTaskCompletion(taskId, goalHash, durationMs, wasSuccessful) {
|
|
66
|
+
const record = {
|
|
67
|
+
taskId,
|
|
68
|
+
goalHash,
|
|
69
|
+
completedAt: new Date(),
|
|
70
|
+
durationMs,
|
|
71
|
+
wasSuccessful,
|
|
72
|
+
};
|
|
73
|
+
const records = this.completionRecords.get(goalHash) ?? [];
|
|
74
|
+
records.push(record);
|
|
75
|
+
const cutoff = new Date();
|
|
76
|
+
cutoff.setDate(cutoff.getDate() - this.velocityWindow);
|
|
77
|
+
const filtered = records.filter((r) => r.completedAt >= cutoff);
|
|
78
|
+
this.completionRecords.set(goalHash, filtered);
|
|
79
|
+
const newVelocity = this.calculateVelocity(goalHash);
|
|
80
|
+
const cached = this.progressCache.get(goalHash);
|
|
81
|
+
if (cached && this.hasVelocityChanged(cached.velocity, newVelocity)) {
|
|
82
|
+
this.emit('velocity:changed', goalHash, newVelocity);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
createSnapshot(goalHash) {
|
|
86
|
+
const progress = this.progressCache.get(goalHash);
|
|
87
|
+
if (!progress) {
|
|
88
|
+
this.logger.warn('PROGRESS_TRACKER', `No progress data for goal ${goalHash}`);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const snapshot = {
|
|
92
|
+
id: `snap_${++this.snapshotCounter}_${Date.now()}`,
|
|
93
|
+
goalHash,
|
|
94
|
+
timestamp: new Date(),
|
|
95
|
+
progress: progress.overallProgress,
|
|
96
|
+
taskStats: { ...progress.taskStats },
|
|
97
|
+
velocity: { ...progress.velocity },
|
|
98
|
+
};
|
|
99
|
+
const snapshots = this.snapshots.get(goalHash) ?? [];
|
|
100
|
+
snapshots.push(snapshot);
|
|
101
|
+
while (snapshots.length > this.maxSnapshots) {
|
|
102
|
+
snapshots.shift();
|
|
103
|
+
}
|
|
104
|
+
this.snapshots.set(goalHash, snapshots);
|
|
105
|
+
this.emit('snapshot:created', snapshot);
|
|
106
|
+
return snapshot;
|
|
107
|
+
}
|
|
108
|
+
getSnapshots(goalHash) {
|
|
109
|
+
return [...(this.snapshots.get(goalHash) ?? [])];
|
|
110
|
+
}
|
|
111
|
+
getCachedProgress(goalHash) {
|
|
112
|
+
return this.progressCache.get(goalHash);
|
|
113
|
+
}
|
|
114
|
+
getProgressSummary(goalHashes) {
|
|
115
|
+
const summary = new Map();
|
|
116
|
+
for (const hash of goalHashes) {
|
|
117
|
+
const progress = this.progressCache.get(hash);
|
|
118
|
+
if (progress) {
|
|
119
|
+
summary.set(hash, progress);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return summary;
|
|
123
|
+
}
|
|
124
|
+
getAggregateStats() {
|
|
125
|
+
const goals = Array.from(this.progressCache.values());
|
|
126
|
+
if (goals.length === 0) {
|
|
127
|
+
return {
|
|
128
|
+
totalGoals: 0,
|
|
129
|
+
averageProgress: 0,
|
|
130
|
+
totalTasks: 0,
|
|
131
|
+
completedTasks: 0,
|
|
132
|
+
averageVelocity: 0,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const totalProgress = goals.reduce((sum, g) => sum + g.overallProgress, 0);
|
|
136
|
+
const totalTasks = goals.reduce((sum, g) => sum + g.taskStats.total, 0);
|
|
137
|
+
const completedTasks = goals.reduce((sum, g) => sum + g.taskStats.completed, 0);
|
|
138
|
+
const totalVelocity = goals.reduce((sum, g) => sum + g.velocity.tasksPerDay, 0);
|
|
139
|
+
return {
|
|
140
|
+
totalGoals: goals.length,
|
|
141
|
+
averageProgress: totalProgress / goals.length,
|
|
142
|
+
totalTasks,
|
|
143
|
+
completedTasks,
|
|
144
|
+
averageVelocity: totalVelocity / goals.length,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
calculateProgressTrend(goalHash, days = 7) {
|
|
148
|
+
const snapshots = this.snapshots.get(goalHash) ?? [];
|
|
149
|
+
const cutoff = new Date();
|
|
150
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
151
|
+
return snapshots
|
|
152
|
+
.filter((s) => s.timestamp >= cutoff)
|
|
153
|
+
.map((s) => ({
|
|
154
|
+
date: s.timestamp,
|
|
155
|
+
progress: s.progress,
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
getCompletionRate(goalHash) {
|
|
159
|
+
const records = this.completionRecords.get(goalHash) ?? [];
|
|
160
|
+
if (records.length === 0)
|
|
161
|
+
return 0;
|
|
162
|
+
const successful = records.filter((r) => r.wasSuccessful).length;
|
|
163
|
+
return successful / records.length;
|
|
164
|
+
}
|
|
165
|
+
clearGoalData(goalHash) {
|
|
166
|
+
this.progressCache.delete(goalHash);
|
|
167
|
+
this.completionRecords.delete(goalHash);
|
|
168
|
+
this.snapshots.delete(goalHash);
|
|
169
|
+
}
|
|
170
|
+
stop() {
|
|
171
|
+
if (this.snapshotTimer) {
|
|
172
|
+
clearInterval(this.snapshotTimer);
|
|
173
|
+
this.snapshotTimer = null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
calculateTaskStats(tasks) {
|
|
177
|
+
const stats = {
|
|
178
|
+
total: tasks.length,
|
|
179
|
+
pending: 0,
|
|
180
|
+
inProgress: 0,
|
|
181
|
+
completed: 0,
|
|
182
|
+
failed: 0,
|
|
183
|
+
skipped: 0,
|
|
184
|
+
blocked: 0,
|
|
185
|
+
expired: 0,
|
|
186
|
+
};
|
|
187
|
+
for (const task of tasks) {
|
|
188
|
+
const status = task.status;
|
|
189
|
+
switch (status) {
|
|
190
|
+
case 'pending':
|
|
191
|
+
stats.pending++;
|
|
192
|
+
break;
|
|
193
|
+
case 'in_progress':
|
|
194
|
+
stats.inProgress++;
|
|
195
|
+
break;
|
|
196
|
+
case 'completed':
|
|
197
|
+
stats.completed++;
|
|
198
|
+
break;
|
|
199
|
+
case 'failed':
|
|
200
|
+
stats.failed++;
|
|
201
|
+
break;
|
|
202
|
+
case 'skipped':
|
|
203
|
+
stats.skipped++;
|
|
204
|
+
break;
|
|
205
|
+
case 'expired':
|
|
206
|
+
stats.expired++;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return stats;
|
|
211
|
+
}
|
|
212
|
+
calculateMilestoneProgress(milestones, tasks) {
|
|
213
|
+
return milestones.map((milestone) => {
|
|
214
|
+
const milestoneTasks = tasks.filter((t) => t.milestoneId === milestone.id);
|
|
215
|
+
const total = milestoneTasks.length;
|
|
216
|
+
const completed = milestoneTasks.filter((t) => t.status === 'completed').length;
|
|
217
|
+
const pending = milestoneTasks.filter((t) => t.status === 'pending').length;
|
|
218
|
+
const failed = milestoneTasks.filter((t) => t.status === 'failed').length;
|
|
219
|
+
const progress = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
220
|
+
return {
|
|
221
|
+
milestoneId: milestone.id,
|
|
222
|
+
title: milestone.title,
|
|
223
|
+
progress,
|
|
224
|
+
tasksTotal: total,
|
|
225
|
+
tasksCompleted: completed,
|
|
226
|
+
tasksPending: pending,
|
|
227
|
+
tasksFailed: failed,
|
|
228
|
+
estimatedCompletion: null,
|
|
229
|
+
};
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
calculateOverallProgress(taskStats, milestoneProgress) {
|
|
233
|
+
if (milestoneProgress.length > 0) {
|
|
234
|
+
const totalTasks = milestoneProgress.reduce((sum, m) => sum + m.tasksTotal, 0);
|
|
235
|
+
if (totalTasks === 0)
|
|
236
|
+
return 0;
|
|
237
|
+
let weightedSum = 0;
|
|
238
|
+
for (const mp of milestoneProgress) {
|
|
239
|
+
weightedSum += mp.progress * mp.tasksTotal;
|
|
240
|
+
}
|
|
241
|
+
return Math.round(weightedSum / totalTasks);
|
|
242
|
+
}
|
|
243
|
+
if (taskStats.total === 0)
|
|
244
|
+
return 0;
|
|
245
|
+
const completable = taskStats.total - taskStats.skipped;
|
|
246
|
+
if (completable === 0)
|
|
247
|
+
return 100;
|
|
248
|
+
return Math.round((taskStats.completed / completable) * 100);
|
|
249
|
+
}
|
|
250
|
+
calculateVelocity(goalHash) {
|
|
251
|
+
const records = this.completionRecords.get(goalHash) ?? [];
|
|
252
|
+
const successfulRecords = records.filter((r) => r.wasSuccessful);
|
|
253
|
+
if (successfulRecords.length < 2) {
|
|
254
|
+
return {
|
|
255
|
+
tasksPerDay: 0,
|
|
256
|
+
averageTaskDuration: 0,
|
|
257
|
+
completionRate: records.length > 0 ? successfulRecords.length / records.length : 0,
|
|
258
|
+
recentTrend: 'stable',
|
|
259
|
+
dataPoints: successfulRecords.length,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const oldest = successfulRecords[0].completedAt;
|
|
263
|
+
const newest = successfulRecords[successfulRecords.length - 1].completedAt;
|
|
264
|
+
const daySpan = (newest.getTime() - oldest.getTime()) / (24 * 60 * 60 * 1000);
|
|
265
|
+
const tasksPerDay = daySpan > 0 ? successfulRecords.length / daySpan : successfulRecords.length;
|
|
266
|
+
const totalDuration = successfulRecords.reduce((sum, r) => sum + r.durationMs, 0);
|
|
267
|
+
const averageTaskDuration = totalDuration / successfulRecords.length;
|
|
268
|
+
const completionRate = successfulRecords.length / records.length;
|
|
269
|
+
const midpoint = Math.floor(successfulRecords.length / 2);
|
|
270
|
+
const recentTrend = this.determineTrend(successfulRecords, midpoint);
|
|
271
|
+
return {
|
|
272
|
+
tasksPerDay: Math.round(tasksPerDay * 100) / 100,
|
|
273
|
+
averageTaskDuration: Math.round(averageTaskDuration),
|
|
274
|
+
completionRate: Math.round(completionRate * 100) / 100,
|
|
275
|
+
recentTrend,
|
|
276
|
+
dataPoints: successfulRecords.length,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
determineTrend(records, midpoint) {
|
|
280
|
+
if (records.length < 4)
|
|
281
|
+
return 'stable';
|
|
282
|
+
const olderHalf = records.slice(0, midpoint);
|
|
283
|
+
const newerHalf = records.slice(midpoint);
|
|
284
|
+
const oldAvgDuration = olderHalf.reduce((sum, r) => sum + r.durationMs, 0) / olderHalf.length;
|
|
285
|
+
const newAvgDuration = newerHalf.reduce((sum, r) => sum + r.durationMs, 0) / newerHalf.length;
|
|
286
|
+
const changeRatio = newAvgDuration / oldAvgDuration;
|
|
287
|
+
if (changeRatio < 0.85)
|
|
288
|
+
return 'improving';
|
|
289
|
+
if (changeRatio > 1.15)
|
|
290
|
+
return 'declining';
|
|
291
|
+
return 'stable';
|
|
292
|
+
}
|
|
293
|
+
estimateCompletion(taskStats, velocity) {
|
|
294
|
+
if (velocity.tasksPerDay <= 0)
|
|
295
|
+
return null;
|
|
296
|
+
const remaining = taskStats.pending + taskStats.inProgress + taskStats.blocked;
|
|
297
|
+
if (remaining === 0)
|
|
298
|
+
return new Date();
|
|
299
|
+
const daysRemaining = remaining / velocity.tasksPerDay;
|
|
300
|
+
const estimate = new Date();
|
|
301
|
+
estimate.setDate(estimate.getDate() + Math.ceil(daysRemaining));
|
|
302
|
+
return estimate;
|
|
303
|
+
}
|
|
304
|
+
hasVelocityChanged(old, newV) {
|
|
305
|
+
return (old.tasksPerDay !== newV.tasksPerDay ||
|
|
306
|
+
old.recentTrend !== newV.recentTrend ||
|
|
307
|
+
Math.abs(old.completionRate - newV.completionRate) > 0.05);
|
|
308
|
+
}
|
|
309
|
+
startAutoSnapshots() {
|
|
310
|
+
this.snapshotTimer = setInterval(() => {
|
|
311
|
+
for (const goalHash of this.progressCache.keys()) {
|
|
312
|
+
try {
|
|
313
|
+
this.createSnapshot(goalHash);
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
this.emit('error', error instanceof Error ? error : new Error(String(error)));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}, this.snapshotInterval);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
export function createProgressTracker(options) {
|
|
323
|
+
return new ProgressTracker(options);
|
|
324
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { Goal, GoalHash, GoalStatus, Logger } from './types.js';
|
|
3
|
+
export type ReviewFrequency = 'daily' | 'weekly' | 'biweekly' | 'monthly' | 'milestone' | 'custom';
|
|
4
|
+
export type ReviewType = 'progress' | 'milestone' | 'stalled' | 'deadline' | 'weekly_summary' | 'monthly_summary' | 'completion';
|
|
5
|
+
export interface ScheduledReview {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly goalHash: GoalHash;
|
|
8
|
+
readonly type: ReviewType;
|
|
9
|
+
readonly scheduledFor: Date;
|
|
10
|
+
readonly frequency: ReviewFrequency;
|
|
11
|
+
readonly title: string;
|
|
12
|
+
readonly description?: string;
|
|
13
|
+
readonly priority: 'high' | 'medium' | 'low';
|
|
14
|
+
readonly metadata?: Record<string, unknown>;
|
|
15
|
+
readonly createdAt: Date;
|
|
16
|
+
readonly completedAt?: Date;
|
|
17
|
+
readonly snoozedUntil?: Date;
|
|
18
|
+
}
|
|
19
|
+
export interface ReviewScheduleConfig {
|
|
20
|
+
readonly goalHash: GoalHash;
|
|
21
|
+
readonly frequency: ReviewFrequency;
|
|
22
|
+
readonly customIntervalDays?: number;
|
|
23
|
+
readonly preferredTime?: string;
|
|
24
|
+
readonly preferredDay?: number;
|
|
25
|
+
readonly enabled: boolean;
|
|
26
|
+
readonly notifyVia?: ('comms' | 'cli' | 'native')[];
|
|
27
|
+
}
|
|
28
|
+
export interface ReviewTrigger {
|
|
29
|
+
readonly type: 'stalled' | 'deadline_approaching' | 'velocity_drop' | 'milestone_complete';
|
|
30
|
+
readonly thresholdDays?: number;
|
|
31
|
+
readonly velocityDropPercent?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ReviewResult {
|
|
34
|
+
readonly reviewId: string;
|
|
35
|
+
readonly action: 'completed' | 'snoozed' | 'skipped' | 'rescheduled';
|
|
36
|
+
readonly notes?: string;
|
|
37
|
+
readonly nextReviewDate?: Date;
|
|
38
|
+
readonly goalUpdates?: {
|
|
39
|
+
statusChange?: GoalStatus;
|
|
40
|
+
priorityChange?: string;
|
|
41
|
+
deadlineChange?: Date;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export interface ReviewSchedulerOptions {
|
|
45
|
+
readonly logger?: Logger;
|
|
46
|
+
readonly checkInterval?: number;
|
|
47
|
+
readonly defaultFrequency?: ReviewFrequency;
|
|
48
|
+
readonly stalledThresholdDays?: number;
|
|
49
|
+
readonly deadlineWarningDays?: number;
|
|
50
|
+
readonly enableAutoTriggers?: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface ReviewSchedulerEvents {
|
|
53
|
+
'review:due': [review: ScheduledReview];
|
|
54
|
+
'review:upcoming': [review: ScheduledReview, daysUntil: number];
|
|
55
|
+
'review:completed': [review: ScheduledReview, result: ReviewResult];
|
|
56
|
+
'review:snoozed': [review: ScheduledReview, until: Date];
|
|
57
|
+
'review:created': [review: ScheduledReview];
|
|
58
|
+
'goal:stalled': [goalHash: GoalHash, daysSinceActivity: number];
|
|
59
|
+
'deadline:approaching': [goalHash: GoalHash, daysRemaining: number];
|
|
60
|
+
error: [error: Error];
|
|
61
|
+
}
|
|
62
|
+
export declare class ReviewScheduler extends EventEmitter<ReviewSchedulerEvents> {
|
|
63
|
+
private readonly logger;
|
|
64
|
+
private readonly checkInterval;
|
|
65
|
+
private readonly stalledThresholdDays;
|
|
66
|
+
private readonly deadlineWarningDays;
|
|
67
|
+
private readonly enableAutoTriggers;
|
|
68
|
+
private readonly schedules;
|
|
69
|
+
private readonly reviews;
|
|
70
|
+
private readonly lastActivityDates;
|
|
71
|
+
private checkTimer;
|
|
72
|
+
private reviewCounter;
|
|
73
|
+
constructor(options?: ReviewSchedulerOptions);
|
|
74
|
+
start(): void;
|
|
75
|
+
stop(): void;
|
|
76
|
+
setSchedule(config: ReviewScheduleConfig): void;
|
|
77
|
+
removeSchedule(goalHash: GoalHash): void;
|
|
78
|
+
scheduleReview(goalHash: GoalHash, type: ReviewType, scheduledFor: Date, frequency?: ReviewFrequency, metadata?: Record<string, unknown>): ScheduledReview;
|
|
79
|
+
completeReview(reviewId: string, result: ReviewResult): void;
|
|
80
|
+
snoozeReview(reviewId: string, hours?: number): void;
|
|
81
|
+
getPendingReviews(goalHash?: GoalHash): ScheduledReview[];
|
|
82
|
+
getDueReviews(): ScheduledReview[];
|
|
83
|
+
getUpcomingReviews(withinHours?: number): ScheduledReview[];
|
|
84
|
+
recordActivity(goalHash: GoalHash): void;
|
|
85
|
+
checkStalledGoals(goals: Goal[]): GoalHash[];
|
|
86
|
+
checkApproachingDeadlines(goals: Goal[]): GoalHash[];
|
|
87
|
+
triggerMilestoneReview(goalHash: GoalHash, milestoneTitle: string): ScheduledReview;
|
|
88
|
+
triggerCompletionReview(goalHash: GoalHash): ScheduledReview;
|
|
89
|
+
getStatistics(): {
|
|
90
|
+
totalScheduled: number;
|
|
91
|
+
completed: number;
|
|
92
|
+
pending: number;
|
|
93
|
+
overdue: number;
|
|
94
|
+
snoozed: number;
|
|
95
|
+
};
|
|
96
|
+
clearGoalData(goalHash: GoalHash): void;
|
|
97
|
+
private checkDueReviews;
|
|
98
|
+
private calculateNextReviewDate;
|
|
99
|
+
private getDaysInMonth;
|
|
100
|
+
private findReview;
|
|
101
|
+
private getReviewTitle;
|
|
102
|
+
private getReviewDescription;
|
|
103
|
+
private getReviewPriority;
|
|
104
|
+
private capitalizeFirst;
|
|
105
|
+
}
|
|
106
|
+
export declare function createReviewScheduler(options?: ReviewSchedulerOptions): ReviewScheduler;
|