@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,534 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
const DEFAULT_PERIOD_DAYS = 7;
|
|
3
|
+
const DEFAULT_SECTIONS = [
|
|
4
|
+
'overview',
|
|
5
|
+
'milestones',
|
|
6
|
+
'tasks',
|
|
7
|
+
'velocity',
|
|
8
|
+
'actions',
|
|
9
|
+
];
|
|
10
|
+
export class GoalReporter extends EventEmitter {
|
|
11
|
+
logger;
|
|
12
|
+
defaultFormat;
|
|
13
|
+
defaultDetailLevel;
|
|
14
|
+
defaultPeriodDays;
|
|
15
|
+
reportCounter = 0;
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
super();
|
|
18
|
+
this.logger = options.logger ?? console;
|
|
19
|
+
this.defaultFormat = options.defaultFormat ?? 'markdown';
|
|
20
|
+
this.defaultDetailLevel = options.defaultDetailLevel ?? 'standard';
|
|
21
|
+
this.defaultPeriodDays = options.defaultPeriodDays ?? DEFAULT_PERIOD_DAYS;
|
|
22
|
+
}
|
|
23
|
+
generateReport(goal, progress, milestones, tasks, snapshots = [], options = {}) {
|
|
24
|
+
const format = options.format ?? this.defaultFormat;
|
|
25
|
+
const detailLevel = options.detailLevel ?? this.defaultDetailLevel;
|
|
26
|
+
const periodDays = options.periodDays ?? this.defaultPeriodDays;
|
|
27
|
+
const sections = options.sections ?? DEFAULT_SECTIONS;
|
|
28
|
+
const period = this.calculatePeriod(periodDays);
|
|
29
|
+
const metadata = this.buildMetadata(progress, options.compareWithSnapshot);
|
|
30
|
+
const sectionContents = [];
|
|
31
|
+
for (const section of sections) {
|
|
32
|
+
const content = this.generateSection(section, goal, progress, milestones, tasks, snapshots, detailLevel, format);
|
|
33
|
+
if (content) {
|
|
34
|
+
sectionContents.push(content);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const fullContent = this.combineContent(sectionContents, format, goal, period);
|
|
38
|
+
const report = {
|
|
39
|
+
id: `report_${++this.reportCounter}_${Date.now()}`,
|
|
40
|
+
goalHash: goal.hash,
|
|
41
|
+
title: `Progress Report: ${goal.name}`,
|
|
42
|
+
generatedAt: new Date(),
|
|
43
|
+
period,
|
|
44
|
+
format,
|
|
45
|
+
detailLevel,
|
|
46
|
+
content: fullContent,
|
|
47
|
+
sections: sectionContents,
|
|
48
|
+
metadata,
|
|
49
|
+
};
|
|
50
|
+
this.emit('report:generated', report);
|
|
51
|
+
const actions = this.identifyActionItems(goal, progress, milestones, tasks);
|
|
52
|
+
if (actions.length > 0) {
|
|
53
|
+
this.emit('actions:identified', goal.hash, actions);
|
|
54
|
+
}
|
|
55
|
+
if (options.includeRisks) {
|
|
56
|
+
const risks = this.assessRisks(goal, progress, milestones, tasks);
|
|
57
|
+
if (risks.length > 0) {
|
|
58
|
+
this.emit('risks:identified', goal.hash, risks);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return report;
|
|
62
|
+
}
|
|
63
|
+
generateSummary(goal, progress) {
|
|
64
|
+
const lines = [];
|
|
65
|
+
lines.push(`📊 **${goal.name}**`);
|
|
66
|
+
lines.push(`Progress: ${progress.overallProgress}%`);
|
|
67
|
+
lines.push(`Tasks: ${progress.taskStats.completed}/${progress.taskStats.total} completed`);
|
|
68
|
+
if (progress.velocity.tasksPerDay > 0) {
|
|
69
|
+
lines.push(`Velocity: ${progress.velocity.tasksPerDay} tasks/day`);
|
|
70
|
+
}
|
|
71
|
+
if (progress.estimatedCompletion) {
|
|
72
|
+
lines.push(`Est. Completion: ${this.formatDate(progress.estimatedCompletion)}`);
|
|
73
|
+
}
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|
|
76
|
+
generateComparisonReport(goal, current, previous, format = 'markdown') {
|
|
77
|
+
const progressDelta = current.overallProgress - previous.progress;
|
|
78
|
+
const tasksCompleted = current.taskStats.completed - previous.taskStats.completed;
|
|
79
|
+
if (format === 'json') {
|
|
80
|
+
return JSON.stringify({
|
|
81
|
+
goalHash: goal.hash,
|
|
82
|
+
title: goal.name,
|
|
83
|
+
current: {
|
|
84
|
+
progress: current.overallProgress,
|
|
85
|
+
tasks: current.taskStats,
|
|
86
|
+
},
|
|
87
|
+
previous: {
|
|
88
|
+
progress: previous.progress,
|
|
89
|
+
tasks: previous.taskStats,
|
|
90
|
+
timestamp: previous.timestamp,
|
|
91
|
+
},
|
|
92
|
+
delta: {
|
|
93
|
+
progress: progressDelta,
|
|
94
|
+
tasksCompleted,
|
|
95
|
+
},
|
|
96
|
+
}, null, 2);
|
|
97
|
+
}
|
|
98
|
+
const lines = [];
|
|
99
|
+
lines.push(`## Progress Comparison: ${goal.name}\n`);
|
|
100
|
+
lines.push(`**Period**: ${this.formatDate(previous.timestamp)} → Now\n`);
|
|
101
|
+
lines.push(`### Changes`);
|
|
102
|
+
lines.push(`- Progress: ${previous.progress}% → ${current.overallProgress}% (${progressDelta >= 0 ? '+' : ''}${progressDelta}%)`);
|
|
103
|
+
lines.push(`- Tasks Completed: +${tasksCompleted}`);
|
|
104
|
+
lines.push(`- Current Velocity: ${current.velocity.tasksPerDay} tasks/day`);
|
|
105
|
+
lines.push(`- Trend: ${this.formatTrend(current.velocity.recentTrend)}`);
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
}
|
|
108
|
+
identifyActionItems(goal, progress, milestones, tasks) {
|
|
109
|
+
const actions = [];
|
|
110
|
+
let actionCounter = 0;
|
|
111
|
+
const failedTasks = tasks.filter((t) => t.status === 'failed');
|
|
112
|
+
for (const task of failedTasks) {
|
|
113
|
+
actions.push({
|
|
114
|
+
id: `action_${++actionCounter}`,
|
|
115
|
+
type: 'blocked_task',
|
|
116
|
+
priority: 'high',
|
|
117
|
+
description: `Task "${task.title}" has failed`,
|
|
118
|
+
relatedTaskId: task.id,
|
|
119
|
+
suggestedAction: 'Review error and retry or skip task',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const now = new Date();
|
|
123
|
+
const overdueTasks = tasks.filter((t) => t.status === 'pending' &&
|
|
124
|
+
t.deadline &&
|
|
125
|
+
new Date(t.deadline) < now);
|
|
126
|
+
for (const task of overdueTasks) {
|
|
127
|
+
actions.push({
|
|
128
|
+
id: `action_${++actionCounter}`,
|
|
129
|
+
type: 'overdue',
|
|
130
|
+
priority: 'high',
|
|
131
|
+
description: `Task "${task.title}" is overdue`,
|
|
132
|
+
relatedTaskId: task.id,
|
|
133
|
+
suggestedAction: 'Reschedule or prioritize immediately',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const tomorrow = new Date();
|
|
137
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
138
|
+
const atRiskTasks = tasks.filter((t) => t.status === 'pending' &&
|
|
139
|
+
t.deadline &&
|
|
140
|
+
new Date(t.deadline) <= tomorrow &&
|
|
141
|
+
new Date(t.deadline) >= now);
|
|
142
|
+
for (const task of atRiskTasks) {
|
|
143
|
+
actions.push({
|
|
144
|
+
id: `action_${++actionCounter}`,
|
|
145
|
+
type: 'at_risk',
|
|
146
|
+
priority: 'medium',
|
|
147
|
+
description: `Task "${task.title}" deadline approaching`,
|
|
148
|
+
relatedTaskId: task.id,
|
|
149
|
+
suggestedAction: 'Prioritize to complete before deadline',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const pendingApproval = tasks.filter((t) => t.status === 'pending' && t.requiresApproval);
|
|
153
|
+
for (const task of pendingApproval) {
|
|
154
|
+
actions.push({
|
|
155
|
+
id: `action_${++actionCounter}`,
|
|
156
|
+
type: 'approval_pending',
|
|
157
|
+
priority: 'medium',
|
|
158
|
+
description: `Task "${task.title}" requires approval`,
|
|
159
|
+
relatedTaskId: task.id,
|
|
160
|
+
suggestedAction: 'Review and approve/deny task',
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
for (const mp of progress.milestoneProgress) {
|
|
164
|
+
if (mp.progress > 0 && mp.progress < 100 && mp.tasksPending === 0 && mp.tasksFailed > 0) {
|
|
165
|
+
const milestone = milestones.find((m) => m.id === mp.milestoneId);
|
|
166
|
+
actions.push({
|
|
167
|
+
id: `action_${++actionCounter}`,
|
|
168
|
+
type: 'review_needed',
|
|
169
|
+
priority: 'high',
|
|
170
|
+
description: `Milestone "${milestone?.title ?? mp.milestoneId}" has failed tasks`,
|
|
171
|
+
relatedMilestoneId: mp.milestoneId,
|
|
172
|
+
suggestedAction: 'Review failed tasks and create recovery plan',
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return actions;
|
|
177
|
+
}
|
|
178
|
+
assessRisks(goal, progress, _milestones, _tasks) {
|
|
179
|
+
const risks = [];
|
|
180
|
+
let riskCounter = 0;
|
|
181
|
+
if (goal.deadline && progress.estimatedCompletion) {
|
|
182
|
+
const deadline = new Date(goal.deadline);
|
|
183
|
+
if (progress.estimatedCompletion > deadline) {
|
|
184
|
+
const daysOver = Math.ceil((progress.estimatedCompletion.getTime() - deadline.getTime()) /
|
|
185
|
+
(24 * 60 * 60 * 1000));
|
|
186
|
+
risks.push({
|
|
187
|
+
id: `risk_${++riskCounter}`,
|
|
188
|
+
severity: daysOver > 7 ? 'high' : daysOver > 3 ? 'medium' : 'low',
|
|
189
|
+
category: 'deadline',
|
|
190
|
+
description: `Goal may miss deadline by ~${daysOver} days`,
|
|
191
|
+
mitigation: 'Increase velocity or reduce scope',
|
|
192
|
+
likelihood: Math.min(0.9, 0.5 + daysOver * 0.05),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (progress.velocity.recentTrend === 'declining') {
|
|
197
|
+
risks.push({
|
|
198
|
+
id: `risk_${++riskCounter}`,
|
|
199
|
+
severity: 'medium',
|
|
200
|
+
category: 'resources',
|
|
201
|
+
description: 'Task completion velocity is declining',
|
|
202
|
+
mitigation: 'Review blockers and resource allocation',
|
|
203
|
+
likelihood: 0.7,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const failureRate = progress.taskStats.total > 0
|
|
207
|
+
? progress.taskStats.failed / progress.taskStats.total
|
|
208
|
+
: 0;
|
|
209
|
+
if (failureRate > 0.2) {
|
|
210
|
+
risks.push({
|
|
211
|
+
id: `risk_${++riskCounter}`,
|
|
212
|
+
severity: failureRate > 0.4 ? 'high' : 'medium',
|
|
213
|
+
category: 'technical',
|
|
214
|
+
description: `High task failure rate (${Math.round(failureRate * 100)}%)`,
|
|
215
|
+
mitigation: 'Analyze failure patterns and address root causes',
|
|
216
|
+
likelihood: 0.8,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
const blockedCount = progress.taskStats.blocked;
|
|
220
|
+
if (blockedCount > 0) {
|
|
221
|
+
risks.push({
|
|
222
|
+
id: `risk_${++riskCounter}`,
|
|
223
|
+
severity: blockedCount > 3 ? 'high' : 'medium',
|
|
224
|
+
category: 'dependencies',
|
|
225
|
+
description: `${blockedCount} tasks are blocked`,
|
|
226
|
+
mitigation: 'Resolve blocking dependencies',
|
|
227
|
+
likelihood: 0.9,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return risks;
|
|
231
|
+
}
|
|
232
|
+
calculatePeriod(days) {
|
|
233
|
+
const end = new Date();
|
|
234
|
+
const start = new Date();
|
|
235
|
+
start.setDate(start.getDate() - days);
|
|
236
|
+
return {
|
|
237
|
+
start,
|
|
238
|
+
end,
|
|
239
|
+
label: `Last ${days} days`,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
buildMetadata(progress, compareSnapshot) {
|
|
243
|
+
return {
|
|
244
|
+
progress: progress.overallProgress,
|
|
245
|
+
taskStats: progress.taskStats,
|
|
246
|
+
velocity: progress.velocity,
|
|
247
|
+
estimatedCompletion: progress.estimatedCompletion,
|
|
248
|
+
previousProgress: compareSnapshot?.progress,
|
|
249
|
+
progressDelta: compareSnapshot
|
|
250
|
+
? progress.overallProgress - compareSnapshot.progress
|
|
251
|
+
: undefined,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
generateSection(section, goal, progress, milestones, tasks, snapshots, detailLevel, format) {
|
|
255
|
+
switch (section) {
|
|
256
|
+
case 'overview':
|
|
257
|
+
return this.generateOverviewSection(goal, progress, format);
|
|
258
|
+
case 'milestones':
|
|
259
|
+
return this.generateMilestonesSection(progress, milestones, detailLevel, format);
|
|
260
|
+
case 'tasks':
|
|
261
|
+
return this.generateTasksSection(progress, tasks, detailLevel, format);
|
|
262
|
+
case 'velocity':
|
|
263
|
+
return this.generateVelocitySection(progress, format);
|
|
264
|
+
case 'history':
|
|
265
|
+
return this.generateHistorySection(snapshots, format);
|
|
266
|
+
case 'actions':
|
|
267
|
+
return this.generateActionsSection(this.identifyActionItems(goal, progress, milestones, tasks), format);
|
|
268
|
+
case 'risks':
|
|
269
|
+
return this.generateRisksSection(this.assessRisks(goal, progress, milestones, tasks), format);
|
|
270
|
+
default:
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
generateOverviewSection(goal, progress, format) {
|
|
275
|
+
let content;
|
|
276
|
+
if (format === 'json') {
|
|
277
|
+
content = JSON.stringify({
|
|
278
|
+
status: goal.status,
|
|
279
|
+
progress: progress.overallProgress,
|
|
280
|
+
estimatedCompletion: progress.estimatedCompletion,
|
|
281
|
+
lastUpdated: progress.lastUpdated,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const lines = [];
|
|
286
|
+
lines.push(`**Status**: ${goal.status}`);
|
|
287
|
+
lines.push(`**Progress**: ${progress.overallProgress}%`);
|
|
288
|
+
lines.push(this.renderProgressBar(progress.overallProgress));
|
|
289
|
+
if (progress.estimatedCompletion) {
|
|
290
|
+
lines.push(`**Est. Completion**: ${this.formatDate(progress.estimatedCompletion)}`);
|
|
291
|
+
}
|
|
292
|
+
if (goal.deadline) {
|
|
293
|
+
lines.push(`**Deadline**: ${this.formatDate(new Date(goal.deadline))}`);
|
|
294
|
+
}
|
|
295
|
+
content = lines.join('\n');
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
section: 'overview',
|
|
299
|
+
title: 'Overview',
|
|
300
|
+
content,
|
|
301
|
+
data: {
|
|
302
|
+
status: goal.status,
|
|
303
|
+
progress: progress.overallProgress,
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
generateMilestonesSection(progress, milestones, detailLevel, format) {
|
|
308
|
+
let content;
|
|
309
|
+
if (format === 'json') {
|
|
310
|
+
content = JSON.stringify(progress.milestoneProgress);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
const lines = [];
|
|
314
|
+
for (const mp of progress.milestoneProgress) {
|
|
315
|
+
const milestone = milestones.find((m) => m.id === mp.milestoneId);
|
|
316
|
+
const statusIcon = mp.progress === 100 ? '✅' : mp.progress > 0 ? '🔄' : '⏳';
|
|
317
|
+
lines.push(`${statusIcon} **${milestone?.title ?? `Milestone ${mp.milestoneId}`}**`);
|
|
318
|
+
lines.push(` ${this.renderProgressBar(mp.progress)} ${mp.progress}%`);
|
|
319
|
+
if (detailLevel !== 'summary') {
|
|
320
|
+
lines.push(` Tasks: ${mp.tasksCompleted}/${mp.tasksTotal} completed`);
|
|
321
|
+
}
|
|
322
|
+
lines.push('');
|
|
323
|
+
}
|
|
324
|
+
content = lines.join('\n');
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
section: 'milestones',
|
|
328
|
+
title: 'Milestones',
|
|
329
|
+
content,
|
|
330
|
+
data: { milestones: progress.milestoneProgress },
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
generateTasksSection(progress, tasks, detailLevel, format) {
|
|
334
|
+
const stats = progress.taskStats;
|
|
335
|
+
let content;
|
|
336
|
+
if (format === 'json') {
|
|
337
|
+
content = JSON.stringify(stats);
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
const lines = [];
|
|
341
|
+
lines.push(`**Total**: ${stats.total}`);
|
|
342
|
+
lines.push(`**Completed**: ${stats.completed} ✅`);
|
|
343
|
+
lines.push(`**In Progress**: ${stats.inProgress} 🔄`);
|
|
344
|
+
lines.push(`**Pending**: ${stats.pending} ⏳`);
|
|
345
|
+
if (stats.blocked > 0)
|
|
346
|
+
lines.push(`**Blocked**: ${stats.blocked} 🚫`);
|
|
347
|
+
if (stats.failed > 0)
|
|
348
|
+
lines.push(`**Failed**: ${stats.failed} ❌`);
|
|
349
|
+
if (stats.skipped > 0)
|
|
350
|
+
lines.push(`**Skipped**: ${stats.skipped} ⏭️`);
|
|
351
|
+
if (detailLevel === 'detailed' || detailLevel === 'full') {
|
|
352
|
+
lines.push('\n**Recent Tasks**:');
|
|
353
|
+
const recentTasks = tasks
|
|
354
|
+
.filter((t) => t.status === 'completed')
|
|
355
|
+
.slice(-5);
|
|
356
|
+
for (const task of recentTasks) {
|
|
357
|
+
lines.push(`- ✅ ${task.title}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
content = lines.join('\n');
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
section: 'tasks',
|
|
364
|
+
title: 'Tasks',
|
|
365
|
+
content,
|
|
366
|
+
data: { ...stats },
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
generateVelocitySection(progress, format) {
|
|
370
|
+
const velocity = progress.velocity;
|
|
371
|
+
let content;
|
|
372
|
+
if (format === 'json') {
|
|
373
|
+
content = JSON.stringify(velocity);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
const lines = [];
|
|
377
|
+
lines.push(`**Tasks/Day**: ${velocity.tasksPerDay}`);
|
|
378
|
+
lines.push(`**Avg Duration**: ${this.formatDuration(velocity.averageTaskDuration)}`);
|
|
379
|
+
lines.push(`**Success Rate**: ${Math.round(velocity.completionRate * 100)}%`);
|
|
380
|
+
lines.push(`**Trend**: ${this.formatTrend(velocity.recentTrend)}`);
|
|
381
|
+
lines.push(`*Based on ${velocity.dataPoints} data points*`);
|
|
382
|
+
content = lines.join('\n');
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
section: 'velocity',
|
|
386
|
+
title: 'Velocity',
|
|
387
|
+
content,
|
|
388
|
+
data: { ...velocity },
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
generateHistorySection(snapshots, format) {
|
|
392
|
+
let content;
|
|
393
|
+
if (format === 'json') {
|
|
394
|
+
content = JSON.stringify(snapshots.slice(-10));
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
if (snapshots.length === 0) {
|
|
398
|
+
content = '*No historical data available*';
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
const lines = [];
|
|
402
|
+
const recent = snapshots.slice(-7);
|
|
403
|
+
for (const snap of recent) {
|
|
404
|
+
lines.push(`- ${this.formatDate(snap.timestamp)}: ${snap.progress}%`);
|
|
405
|
+
}
|
|
406
|
+
content = lines.join('\n');
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
section: 'history',
|
|
411
|
+
title: 'History',
|
|
412
|
+
content,
|
|
413
|
+
data: { snapshots: snapshots.slice(-10) },
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
generateActionsSection(actions, format) {
|
|
417
|
+
let content;
|
|
418
|
+
if (format === 'json') {
|
|
419
|
+
content = JSON.stringify(actions);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
if (actions.length === 0) {
|
|
423
|
+
content = '✅ No action items';
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
const lines = [];
|
|
427
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
428
|
+
const sorted = [...actions].sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
429
|
+
for (const action of sorted) {
|
|
430
|
+
const icon = action.priority === 'high'
|
|
431
|
+
? '🔴'
|
|
432
|
+
: action.priority === 'medium'
|
|
433
|
+
? '🟡'
|
|
434
|
+
: '🟢';
|
|
435
|
+
lines.push(`${icon} **${action.type}**: ${action.description}`);
|
|
436
|
+
if (action.suggestedAction) {
|
|
437
|
+
lines.push(` → ${action.suggestedAction}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
content = lines.join('\n');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
section: 'actions',
|
|
445
|
+
title: 'Action Items',
|
|
446
|
+
content,
|
|
447
|
+
data: { actions },
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
generateRisksSection(risks, format) {
|
|
451
|
+
let content;
|
|
452
|
+
if (format === 'json') {
|
|
453
|
+
content = JSON.stringify(risks);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
if (risks.length === 0) {
|
|
457
|
+
content = '✅ No significant risks identified';
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
const lines = [];
|
|
461
|
+
for (const risk of risks) {
|
|
462
|
+
const icon = risk.severity === 'high'
|
|
463
|
+
? '🔴'
|
|
464
|
+
: risk.severity === 'medium'
|
|
465
|
+
? '🟡'
|
|
466
|
+
: '🟢';
|
|
467
|
+
lines.push(`${icon} **${risk.category}**: ${risk.description}`);
|
|
468
|
+
if (risk.mitigation) {
|
|
469
|
+
lines.push(` Mitigation: ${risk.mitigation}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
content = lines.join('\n');
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
section: 'risks',
|
|
477
|
+
title: 'Risks',
|
|
478
|
+
content,
|
|
479
|
+
data: { risks },
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
combineContent(sections, format, goal, period) {
|
|
483
|
+
if (format === 'json') {
|
|
484
|
+
return JSON.stringify({
|
|
485
|
+
goal: { hash: goal.hash, title: goal.name },
|
|
486
|
+
period,
|
|
487
|
+
sections: sections.map((s) => ({ section: s.section, data: s.data })),
|
|
488
|
+
}, null, 2);
|
|
489
|
+
}
|
|
490
|
+
const lines = [];
|
|
491
|
+
lines.push(`# Progress Report: ${goal.name}`);
|
|
492
|
+
lines.push(`*Generated: ${this.formatDate(new Date())}*`);
|
|
493
|
+
lines.push(`*Period: ${period.label}*\n`);
|
|
494
|
+
for (const section of sections) {
|
|
495
|
+
lines.push(`## ${section.title}\n`);
|
|
496
|
+
lines.push(section.content);
|
|
497
|
+
lines.push('');
|
|
498
|
+
}
|
|
499
|
+
return lines.join('\n');
|
|
500
|
+
}
|
|
501
|
+
renderProgressBar(percent) {
|
|
502
|
+
const filled = Math.round(percent / 10);
|
|
503
|
+
const empty = 10 - filled;
|
|
504
|
+
return `[${'█'.repeat(filled)}${'░'.repeat(empty)}]`;
|
|
505
|
+
}
|
|
506
|
+
formatDate(date) {
|
|
507
|
+
return date.toLocaleDateString('en-US', {
|
|
508
|
+
year: 'numeric',
|
|
509
|
+
month: 'short',
|
|
510
|
+
day: 'numeric',
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
formatDuration(ms) {
|
|
514
|
+
if (ms < 60000)
|
|
515
|
+
return `${Math.round(ms / 1000)}s`;
|
|
516
|
+
if (ms < 3600000)
|
|
517
|
+
return `${Math.round(ms / 60000)}m`;
|
|
518
|
+
return `${Math.round(ms / 3600000)}h`;
|
|
519
|
+
}
|
|
520
|
+
formatTrend(trend) {
|
|
521
|
+
switch (trend) {
|
|
522
|
+
case 'improving':
|
|
523
|
+
return '📈 Improving';
|
|
524
|
+
case 'declining':
|
|
525
|
+
return '📉 Declining';
|
|
526
|
+
case 'stable':
|
|
527
|
+
default:
|
|
528
|
+
return '➡️ Stable';
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
export function createGoalReporter(options) {
|
|
533
|
+
return new GoalReporter(options);
|
|
534
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { GoalHash, Logger } from './types.js';
|
|
3
|
+
export interface ScheduledGoal {
|
|
4
|
+
id: number;
|
|
5
|
+
hash: GoalHash;
|
|
6
|
+
userId: number;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string | null;
|
|
9
|
+
approvalMode: 'full' | 'category' | 'trusted';
|
|
10
|
+
scheduleType: 'cron' | 'interval';
|
|
11
|
+
cronExpression: string | null;
|
|
12
|
+
intervalMinutes: number | null;
|
|
13
|
+
scheduleTimezone: string;
|
|
14
|
+
executionRecipe: string | null;
|
|
15
|
+
maxIterations: number;
|
|
16
|
+
maxRuntimeMinutes: number;
|
|
17
|
+
llmTier: 'simple' | 'capable' | 'complex';
|
|
18
|
+
allowedActions: string[] | null;
|
|
19
|
+
blockedActions: string[] | null;
|
|
20
|
+
nextRunAt: string;
|
|
21
|
+
consecutiveFailures: number;
|
|
22
|
+
user: {
|
|
23
|
+
id: number;
|
|
24
|
+
name: string;
|
|
25
|
+
email: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export type GetDueGoalsFunction = () => Promise<ScheduledGoal[]>;
|
|
29
|
+
export type MarkGoalStartedFunction = (goalId: number, metadata?: Record<string, unknown>) => Promise<void>;
|
|
30
|
+
export type MarkGoalCompletedFunction = (goalId: number, status: 'success' | 'failed', options?: {
|
|
31
|
+
error?: string;
|
|
32
|
+
progress?: number;
|
|
33
|
+
}) => Promise<{
|
|
34
|
+
nextRunAt: string | null;
|
|
35
|
+
scheduleActive: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
export type ExecuteGoalFunction = (goal: ScheduledGoal) => Promise<{
|
|
38
|
+
success: boolean;
|
|
39
|
+
data?: unknown;
|
|
40
|
+
error?: string;
|
|
41
|
+
}>;
|
|
42
|
+
export interface GoalSchedulerOptions {
|
|
43
|
+
getDueGoals: GetDueGoalsFunction;
|
|
44
|
+
markGoalStarted: MarkGoalStartedFunction;
|
|
45
|
+
markGoalCompleted: MarkGoalCompletedFunction;
|
|
46
|
+
executeGoal: ExecuteGoalFunction;
|
|
47
|
+
pollIntervalMs?: number;
|
|
48
|
+
maxConcurrentExecutions?: number;
|
|
49
|
+
logger?: Logger;
|
|
50
|
+
}
|
|
51
|
+
export interface GoalSchedulerEvents {
|
|
52
|
+
'poll-started': [];
|
|
53
|
+
'poll-completed': [dueGoalsCount: number];
|
|
54
|
+
'execution-started': [goal: ScheduledGoal];
|
|
55
|
+
'execution-completed': [goal: ScheduledGoal, success: boolean, error?: string];
|
|
56
|
+
'execution-skipped': [goal: ScheduledGoal, reason: string];
|
|
57
|
+
error: [error: Error, context?: Record<string, unknown>];
|
|
58
|
+
}
|
|
59
|
+
interface ExecutionState {
|
|
60
|
+
goalHash: GoalHash;
|
|
61
|
+
goalId: number;
|
|
62
|
+
startedAt: Date;
|
|
63
|
+
status: 'running' | 'completed' | 'failed';
|
|
64
|
+
}
|
|
65
|
+
export declare class GoalScheduler extends EventEmitter {
|
|
66
|
+
private readonly getDueGoals;
|
|
67
|
+
private readonly markGoalStarted;
|
|
68
|
+
private readonly markGoalCompleted;
|
|
69
|
+
private readonly executeGoal;
|
|
70
|
+
private readonly pollIntervalMs;
|
|
71
|
+
private readonly maxConcurrentExecutions;
|
|
72
|
+
private readonly logger;
|
|
73
|
+
private pollTimer;
|
|
74
|
+
private isPolling;
|
|
75
|
+
private readonly activeExecutions;
|
|
76
|
+
private started;
|
|
77
|
+
private startedAt;
|
|
78
|
+
private totalPollCount;
|
|
79
|
+
private totalExecutionCount;
|
|
80
|
+
private totalSuccessCount;
|
|
81
|
+
private totalFailureCount;
|
|
82
|
+
constructor(options: GoalSchedulerOptions);
|
|
83
|
+
start(): void;
|
|
84
|
+
stop(): void;
|
|
85
|
+
isRunning(): boolean;
|
|
86
|
+
poll(): Promise<void>;
|
|
87
|
+
private executeGoalInBackground;
|
|
88
|
+
getActiveExecutions(): ExecutionState[];
|
|
89
|
+
isGoalExecuting(goalHash: GoalHash): boolean;
|
|
90
|
+
getStats(): {
|
|
91
|
+
running: boolean;
|
|
92
|
+
startedAt: Date | null;
|
|
93
|
+
activeExecutions: number;
|
|
94
|
+
totalPollCount: number;
|
|
95
|
+
totalExecutionCount: number;
|
|
96
|
+
totalSuccessCount: number;
|
|
97
|
+
totalFailureCount: number;
|
|
98
|
+
};
|
|
99
|
+
triggerPollNow(): Promise<void>;
|
|
100
|
+
}
|
|
101
|
+
export declare function createGoalScheduler(options: GoalSchedulerOptions): GoalScheduler;
|
|
102
|
+
export {};
|