@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,326 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { GoalError } from './errors.js';
|
|
3
|
+
const noopLogger = {
|
|
4
|
+
debug: () => { },
|
|
5
|
+
info: () => { },
|
|
6
|
+
warn: () => { },
|
|
7
|
+
error: () => { },
|
|
8
|
+
};
|
|
9
|
+
const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
10
|
+
const DEFAULT_MAX_CACHE_ENTRIES = 1000;
|
|
11
|
+
const DEFAULT_MAX_AUDIT_ENTRIES = 10000;
|
|
12
|
+
const CATEGORY_AUTO_APPROVE = new Set([
|
|
13
|
+
'file-read',
|
|
14
|
+
'web-search',
|
|
15
|
+
]);
|
|
16
|
+
const ALWAYS_REQUIRE_APPROVAL = new Set([
|
|
17
|
+
'command-execute',
|
|
18
|
+
]);
|
|
19
|
+
const DANGEROUS_PATTERNS = [
|
|
20
|
+
/\.env/i,
|
|
21
|
+
/\.ssh/i,
|
|
22
|
+
/\.git\/config/i,
|
|
23
|
+
/credentials/i,
|
|
24
|
+
/secret/i,
|
|
25
|
+
/\.pem$/i,
|
|
26
|
+
/\.key$/i,
|
|
27
|
+
/rm\s+-rf/i,
|
|
28
|
+
/sudo/i,
|
|
29
|
+
/git\s+push/i,
|
|
30
|
+
/npm\s+publish/i,
|
|
31
|
+
/chmod\s+777/i,
|
|
32
|
+
/mkfs/i,
|
|
33
|
+
/dd\s+if=/i,
|
|
34
|
+
];
|
|
35
|
+
export class PermissionBridgeError extends GoalError {
|
|
36
|
+
constructor(message, cause) {
|
|
37
|
+
super('PERMISSION_ERROR', message, cause ? { cause } : {});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class PermissionBridge extends EventEmitter {
|
|
41
|
+
logger;
|
|
42
|
+
defaultApprovalMode;
|
|
43
|
+
cacheTtlMs;
|
|
44
|
+
maxCacheEntries;
|
|
45
|
+
enableAudit;
|
|
46
|
+
maxAuditEntries;
|
|
47
|
+
permissionsProvider;
|
|
48
|
+
cache = new Map();
|
|
49
|
+
auditLog = [];
|
|
50
|
+
sessionPermissions = new Map();
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
super();
|
|
53
|
+
this.logger = options.logger ?? noopLogger;
|
|
54
|
+
this.defaultApprovalMode = options.defaultApprovalMode ?? 'full';
|
|
55
|
+
this.cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
56
|
+
this.maxCacheEntries = options.maxCacheEntries ?? DEFAULT_MAX_CACHE_ENTRIES;
|
|
57
|
+
this.enableAudit = options.enableAudit ?? true;
|
|
58
|
+
this.maxAuditEntries = options.maxAuditEntries ?? DEFAULT_MAX_AUDIT_ENTRIES;
|
|
59
|
+
this.permissionsProvider = options.permissionsProvider;
|
|
60
|
+
}
|
|
61
|
+
async checkPermission(category, context, options = {}) {
|
|
62
|
+
try {
|
|
63
|
+
if (this.isDangerous(category, options)) {
|
|
64
|
+
const result = this.createResult(false, true, category, context.goal.approvalMode, 'Action matches dangerous pattern');
|
|
65
|
+
this.emit('permission-checked', result, context);
|
|
66
|
+
this.emit('approval-required', context);
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
const restrictionResult = this.checkGoalRestrictions(context.goal.restrictions, category, options);
|
|
70
|
+
if (!restrictionResult.allowed) {
|
|
71
|
+
const result = this.createResult(false, false, category, context.goal.approvalMode, restrictionResult.reason);
|
|
72
|
+
this.emit('permission-checked', result, context);
|
|
73
|
+
this.emit('permission-denied', result, context);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
if (options.useCache !== false) {
|
|
77
|
+
const cached = this.checkCache(category, options);
|
|
78
|
+
if (cached !== null) {
|
|
79
|
+
this.emit('cache-hit', { category, decision: cached });
|
|
80
|
+
const result = this.createResultFromCached(cached, category, context.goal.approvalMode);
|
|
81
|
+
this.emit('permission-checked', result, context);
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
this.emit('cache-miss', { category });
|
|
85
|
+
}
|
|
86
|
+
const storedDecision = await this.checkStoredPermissions(category, options);
|
|
87
|
+
if (storedDecision !== null) {
|
|
88
|
+
this.cacheDecision(category, storedDecision, options);
|
|
89
|
+
const result = this.createResultFromCached(storedDecision, category, context.goal.approvalMode);
|
|
90
|
+
this.emit('permission-checked', result, context);
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
const sessionDecision = this.checkSessionPermissions(category, options);
|
|
94
|
+
if (sessionDecision !== null) {
|
|
95
|
+
const result = this.createResultFromCached(sessionDecision, category, context.goal.approvalMode);
|
|
96
|
+
this.emit('permission-checked', result, context);
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
const result = this.applyApprovalMode(category, context.goal.approvalMode);
|
|
100
|
+
this.emit('permission-checked', result, context);
|
|
101
|
+
if (result.requiresApproval) {
|
|
102
|
+
this.emit('approval-required', context);
|
|
103
|
+
}
|
|
104
|
+
else if (result.allowed) {
|
|
105
|
+
this.emit('permission-granted', result, context);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
this.emit('permission-denied', result, context);
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
114
|
+
this.logger.error('PERMISSION_BRIDGE', 'Permission check failed', {
|
|
115
|
+
error: err.message,
|
|
116
|
+
});
|
|
117
|
+
this.emit('error', err);
|
|
118
|
+
return this.createResult(false, false, category, context.goal.approvalMode, 'Permission check failed');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
recordDecision(category, decision, options = {}) {
|
|
122
|
+
const key = this.getCacheKey(category, options);
|
|
123
|
+
if (options.scope === 'session' || !options.scope) {
|
|
124
|
+
this.sessionPermissions.set(key, decision);
|
|
125
|
+
}
|
|
126
|
+
this.cacheDecision(category, decision, options);
|
|
127
|
+
this.logger.debug('PERMISSION_BRIDGE', 'Decision recorded', {
|
|
128
|
+
category,
|
|
129
|
+
decision,
|
|
130
|
+
scope: options.scope ?? 'session',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
logAudit(context, category, decision, approvalRequested, approvalGranted, target) {
|
|
134
|
+
if (!this.enableAudit)
|
|
135
|
+
return;
|
|
136
|
+
const entry = {
|
|
137
|
+
timestamp: new Date(),
|
|
138
|
+
goalHash: context.goal.hash,
|
|
139
|
+
taskId: context.task.id,
|
|
140
|
+
category,
|
|
141
|
+
decision,
|
|
142
|
+
approvalRequested,
|
|
143
|
+
approvalGranted,
|
|
144
|
+
target,
|
|
145
|
+
};
|
|
146
|
+
this.auditLog.push(entry);
|
|
147
|
+
this.emit('audit-logged', entry);
|
|
148
|
+
while (this.auditLog.length > this.maxAuditEntries) {
|
|
149
|
+
this.auditLog.shift();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
checkGoalRestrictions(restrictions, category, _options) {
|
|
153
|
+
if (!restrictions) {
|
|
154
|
+
return { allowed: true };
|
|
155
|
+
}
|
|
156
|
+
if (restrictions.blockedActions?.includes(category)) {
|
|
157
|
+
return { allowed: false, reason: `Category ${category} is blocked by goal restrictions` };
|
|
158
|
+
}
|
|
159
|
+
if (restrictions.allowedCategories &&
|
|
160
|
+
!restrictions.allowedCategories.includes(category)) {
|
|
161
|
+
return { allowed: false, reason: `Category ${category} is not in allowed list` };
|
|
162
|
+
}
|
|
163
|
+
return { allowed: true };
|
|
164
|
+
}
|
|
165
|
+
isDangerous(category, options) {
|
|
166
|
+
const target = options.path ?? options.command ?? options.domain ?? '';
|
|
167
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
168
|
+
if (pattern.test(target)) {
|
|
169
|
+
this.logger.warn('PERMISSION_BRIDGE', 'Dangerous pattern detected', {
|
|
170
|
+
pattern: pattern.toString(),
|
|
171
|
+
target,
|
|
172
|
+
});
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
applyApprovalMode(category, approvalMode) {
|
|
179
|
+
switch (approvalMode) {
|
|
180
|
+
case 'full':
|
|
181
|
+
return this.createResult(false, true, category, approvalMode);
|
|
182
|
+
case 'category':
|
|
183
|
+
if (CATEGORY_AUTO_APPROVE.has(category)) {
|
|
184
|
+
return this.createResult(true, false, category, approvalMode);
|
|
185
|
+
}
|
|
186
|
+
return this.createResult(false, true, category, approvalMode);
|
|
187
|
+
case 'trusted':
|
|
188
|
+
if (ALWAYS_REQUIRE_APPROVAL.has(category)) {
|
|
189
|
+
return this.createResult(false, true, category, approvalMode);
|
|
190
|
+
}
|
|
191
|
+
return this.createResult(true, false, category, approvalMode);
|
|
192
|
+
default:
|
|
193
|
+
return this.createResult(false, true, category, approvalMode);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
checkCache(category, options) {
|
|
197
|
+
const key = this.getCacheKey(category, options);
|
|
198
|
+
const entry = this.cache.get(key);
|
|
199
|
+
if (!entry)
|
|
200
|
+
return null;
|
|
201
|
+
if (Date.now() > entry.expiresAt) {
|
|
202
|
+
this.cache.delete(key);
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
return entry.decision;
|
|
206
|
+
}
|
|
207
|
+
cacheDecision(category, decision, options) {
|
|
208
|
+
const key = this.getCacheKey(category, options);
|
|
209
|
+
if (this.cache.size >= this.maxCacheEntries) {
|
|
210
|
+
const toRemove = Math.floor(this.maxCacheEntries * 0.1);
|
|
211
|
+
const keys = Array.from(this.cache.keys());
|
|
212
|
+
for (let i = 0; i < toRemove; i++) {
|
|
213
|
+
this.cache.delete(keys[i]);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
this.cache.set(key, {
|
|
217
|
+
decision,
|
|
218
|
+
expiresAt: Date.now() + this.cacheTtlMs,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
getCacheKey(category, options) {
|
|
222
|
+
const pattern = options.path ?? options.command ?? options.domain ?? '*';
|
|
223
|
+
return `${category}:${pattern}`;
|
|
224
|
+
}
|
|
225
|
+
clearCache() {
|
|
226
|
+
this.cache.clear();
|
|
227
|
+
this.logger.debug('PERMISSION_BRIDGE', 'Cache cleared');
|
|
228
|
+
}
|
|
229
|
+
async checkStoredPermissions(category, options) {
|
|
230
|
+
if (!this.permissionsProvider)
|
|
231
|
+
return null;
|
|
232
|
+
try {
|
|
233
|
+
const permissions = await this.permissionsProvider();
|
|
234
|
+
const pattern = options.path ?? options.command ?? options.domain;
|
|
235
|
+
for (const perm of permissions) {
|
|
236
|
+
if (perm.category !== category)
|
|
237
|
+
continue;
|
|
238
|
+
if (perm.expiresAt && new Date(perm.expiresAt) < new Date()) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (perm.pattern && pattern) {
|
|
242
|
+
if (!this.matchesPattern(pattern, perm.pattern)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return perm.decision;
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
this.logger.warn('PERMISSION_BRIDGE', 'Failed to load stored permissions', {
|
|
252
|
+
error: error instanceof Error ? error.message : String(error),
|
|
253
|
+
});
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
checkSessionPermissions(category, options) {
|
|
258
|
+
const key = this.getCacheKey(category, options);
|
|
259
|
+
return this.sessionPermissions.get(key) ?? null;
|
|
260
|
+
}
|
|
261
|
+
matchesPattern(value, pattern) {
|
|
262
|
+
const regex = new RegExp('^' +
|
|
263
|
+
pattern
|
|
264
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
265
|
+
.replace(/\*/g, '.*')
|
|
266
|
+
.replace(/\?/g, '.') +
|
|
267
|
+
'$', 'i');
|
|
268
|
+
return regex.test(value);
|
|
269
|
+
}
|
|
270
|
+
createResult(allowed, requiresApproval, category, approvalMode, reason) {
|
|
271
|
+
return {
|
|
272
|
+
allowed,
|
|
273
|
+
requiresApproval,
|
|
274
|
+
reason,
|
|
275
|
+
approvalMode,
|
|
276
|
+
category,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
createResultFromCached(decision, category, approvalMode) {
|
|
280
|
+
switch (decision) {
|
|
281
|
+
case 'allow':
|
|
282
|
+
return this.createResult(true, false, category, approvalMode);
|
|
283
|
+
case 'deny':
|
|
284
|
+
return this.createResult(false, false, category, approvalMode, 'Previously denied');
|
|
285
|
+
case 'ask':
|
|
286
|
+
return this.createResult(false, true, category, approvalMode);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
getAuditLog(filters) {
|
|
290
|
+
let entries = this.auditLog;
|
|
291
|
+
if (filters?.goalHash) {
|
|
292
|
+
entries = entries.filter((e) => e.goalHash === filters.goalHash);
|
|
293
|
+
}
|
|
294
|
+
if (filters?.taskId) {
|
|
295
|
+
entries = entries.filter((e) => e.taskId === filters.taskId);
|
|
296
|
+
}
|
|
297
|
+
if (filters?.category) {
|
|
298
|
+
entries = entries.filter((e) => e.category === filters.category);
|
|
299
|
+
}
|
|
300
|
+
if (filters?.since) {
|
|
301
|
+
entries = entries.filter((e) => e.timestamp >= filters.since);
|
|
302
|
+
}
|
|
303
|
+
if (filters?.limit) {
|
|
304
|
+
entries = entries.slice(-filters.limit);
|
|
305
|
+
}
|
|
306
|
+
return entries;
|
|
307
|
+
}
|
|
308
|
+
clearAuditLog() {
|
|
309
|
+
this.auditLog.length = 0;
|
|
310
|
+
this.logger.debug('PERMISSION_BRIDGE', 'Audit log cleared');
|
|
311
|
+
}
|
|
312
|
+
clearSessionPermissions() {
|
|
313
|
+
this.sessionPermissions.clear();
|
|
314
|
+
this.logger.debug('PERMISSION_BRIDGE', 'Session permissions cleared');
|
|
315
|
+
}
|
|
316
|
+
getCacheStats() {
|
|
317
|
+
return {
|
|
318
|
+
cacheSize: this.cache.size,
|
|
319
|
+
sessionPermissions: this.sessionPermissions.size,
|
|
320
|
+
auditEntries: this.auditLog.length,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
export function createPermissionBridge(options = {}) {
|
|
325
|
+
return new PermissionBridge(options);
|
|
326
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { Goal, GoalHash, Milestone, MilestoneId, Task, Logger } from './types.js';
|
|
3
|
+
export interface GoalProgress {
|
|
4
|
+
readonly goalHash: GoalHash;
|
|
5
|
+
readonly title: string;
|
|
6
|
+
readonly status: string;
|
|
7
|
+
readonly overallProgress: number;
|
|
8
|
+
readonly milestoneProgress: MilestoneProgress[];
|
|
9
|
+
readonly taskStats: TaskStats;
|
|
10
|
+
readonly velocity: VelocityMetrics;
|
|
11
|
+
readonly estimatedCompletion: Date | null;
|
|
12
|
+
readonly lastUpdated: Date;
|
|
13
|
+
}
|
|
14
|
+
export interface MilestoneProgress {
|
|
15
|
+
readonly milestoneId: MilestoneId;
|
|
16
|
+
readonly title: string;
|
|
17
|
+
readonly progress: number;
|
|
18
|
+
readonly tasksTotal: number;
|
|
19
|
+
readonly tasksCompleted: number;
|
|
20
|
+
readonly tasksPending: number;
|
|
21
|
+
readonly tasksFailed: number;
|
|
22
|
+
readonly estimatedCompletion: Date | null;
|
|
23
|
+
}
|
|
24
|
+
export interface TaskStats {
|
|
25
|
+
readonly total: number;
|
|
26
|
+
readonly pending: number;
|
|
27
|
+
readonly inProgress: number;
|
|
28
|
+
readonly completed: number;
|
|
29
|
+
readonly failed: number;
|
|
30
|
+
readonly skipped: number;
|
|
31
|
+
readonly blocked: number;
|
|
32
|
+
readonly expired: number;
|
|
33
|
+
}
|
|
34
|
+
export interface VelocityMetrics {
|
|
35
|
+
readonly tasksPerDay: number;
|
|
36
|
+
readonly averageTaskDuration: number;
|
|
37
|
+
readonly completionRate: number;
|
|
38
|
+
readonly recentTrend: 'improving' | 'stable' | 'declining';
|
|
39
|
+
readonly dataPoints: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ProgressSnapshot {
|
|
42
|
+
readonly id: string;
|
|
43
|
+
readonly goalHash: GoalHash;
|
|
44
|
+
readonly timestamp: Date;
|
|
45
|
+
readonly progress: number;
|
|
46
|
+
readonly taskStats: TaskStats;
|
|
47
|
+
readonly velocity: VelocityMetrics;
|
|
48
|
+
}
|
|
49
|
+
export interface TaskCompletionRecord {
|
|
50
|
+
readonly taskId: number;
|
|
51
|
+
readonly goalHash: GoalHash;
|
|
52
|
+
readonly completedAt: Date;
|
|
53
|
+
readonly durationMs: number;
|
|
54
|
+
readonly wasSuccessful: boolean;
|
|
55
|
+
}
|
|
56
|
+
export interface ProgressTrackerOptions {
|
|
57
|
+
readonly logger?: Logger;
|
|
58
|
+
readonly snapshotInterval?: number;
|
|
59
|
+
readonly velocityWindow?: number;
|
|
60
|
+
readonly maxSnapshots?: number;
|
|
61
|
+
readonly enableAutoSnapshots?: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface ProgressTrackerEvents {
|
|
64
|
+
'progress:updated': [goalHash: GoalHash, progress: GoalProgress];
|
|
65
|
+
'milestone:completed': [goalHash: GoalHash, milestoneId: MilestoneId];
|
|
66
|
+
'goal:completed': [goalHash: GoalHash];
|
|
67
|
+
'velocity:changed': [goalHash: GoalHash, velocity: VelocityMetrics];
|
|
68
|
+
'snapshot:created': [snapshot: ProgressSnapshot];
|
|
69
|
+
'estimate:updated': [goalHash: GoalHash, estimate: Date | null];
|
|
70
|
+
error: [error: Error];
|
|
71
|
+
}
|
|
72
|
+
export declare class ProgressTracker extends EventEmitter<ProgressTrackerEvents> {
|
|
73
|
+
private readonly logger;
|
|
74
|
+
private readonly snapshotInterval;
|
|
75
|
+
private readonly velocityWindow;
|
|
76
|
+
private readonly maxSnapshots;
|
|
77
|
+
private readonly enableAutoSnapshots;
|
|
78
|
+
private readonly completionRecords;
|
|
79
|
+
private readonly snapshots;
|
|
80
|
+
private readonly progressCache;
|
|
81
|
+
private snapshotTimer;
|
|
82
|
+
private snapshotCounter;
|
|
83
|
+
constructor(options?: ProgressTrackerOptions);
|
|
84
|
+
calculateProgress(goal: Goal, milestones: Milestone[], tasks: Task[]): GoalProgress;
|
|
85
|
+
recordTaskCompletion(taskId: number, goalHash: GoalHash, durationMs: number, wasSuccessful: boolean): void;
|
|
86
|
+
createSnapshot(goalHash: GoalHash): ProgressSnapshot | null;
|
|
87
|
+
getSnapshots(goalHash: GoalHash): ProgressSnapshot[];
|
|
88
|
+
getCachedProgress(goalHash: GoalHash): GoalProgress | undefined;
|
|
89
|
+
getProgressSummary(goalHashes: GoalHash[]): Map<GoalHash, GoalProgress>;
|
|
90
|
+
getAggregateStats(): {
|
|
91
|
+
totalGoals: number;
|
|
92
|
+
averageProgress: number;
|
|
93
|
+
totalTasks: number;
|
|
94
|
+
completedTasks: number;
|
|
95
|
+
averageVelocity: number;
|
|
96
|
+
};
|
|
97
|
+
calculateProgressTrend(goalHash: GoalHash, days?: number): {
|
|
98
|
+
date: Date;
|
|
99
|
+
progress: number;
|
|
100
|
+
}[];
|
|
101
|
+
getCompletionRate(goalHash: GoalHash): number;
|
|
102
|
+
clearGoalData(goalHash: GoalHash): void;
|
|
103
|
+
stop(): void;
|
|
104
|
+
private calculateTaskStats;
|
|
105
|
+
private calculateMilestoneProgress;
|
|
106
|
+
private calculateOverallProgress;
|
|
107
|
+
private calculateVelocity;
|
|
108
|
+
private determineTrend;
|
|
109
|
+
private estimateCompletion;
|
|
110
|
+
private hasVelocityChanged;
|
|
111
|
+
private startAutoSnapshots;
|
|
112
|
+
}
|
|
113
|
+
export declare function createProgressTracker(options?: ProgressTrackerOptions): ProgressTracker;
|