gthinking 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/README.md +283 -0
- package/analysis.ts +986 -0
- package/creativity.ts +1002 -0
- package/dist/analysis.d.ts +52 -0
- package/dist/analysis.d.ts.map +1 -0
- package/dist/analysis.js +792 -0
- package/dist/analysis.js.map +1 -0
- package/dist/creativity.d.ts +80 -0
- package/dist/creativity.d.ts.map +1 -0
- package/dist/creativity.js +778 -0
- package/dist/creativity.js.map +1 -0
- package/dist/engine.d.ts +76 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +675 -0
- package/dist/engine.js.map +1 -0
- package/dist/examples.d.ts +7 -0
- package/dist/examples.d.ts.map +1 -0
- package/dist/examples.js +506 -0
- package/dist/examples.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +126 -0
- package/dist/index.js.map +1 -0
- package/dist/learning.d.ts +72 -0
- package/dist/learning.d.ts.map +1 -0
- package/dist/learning.js +615 -0
- package/dist/learning.js.map +1 -0
- package/dist/planning.d.ts +58 -0
- package/dist/planning.d.ts.map +1 -0
- package/dist/planning.js +824 -0
- package/dist/planning.js.map +1 -0
- package/dist/reasoning.d.ts +72 -0
- package/dist/reasoning.d.ts.map +1 -0
- package/dist/reasoning.js +792 -0
- package/dist/reasoning.js.map +1 -0
- package/dist/search-discovery.d.ts +73 -0
- package/dist/search-discovery.d.ts.map +1 -0
- package/dist/search-discovery.js +505 -0
- package/dist/search-discovery.js.map +1 -0
- package/dist/types.d.ts +535 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +77 -0
- package/dist/types.js.map +1 -0
- package/engine.ts +928 -0
- package/examples.ts +717 -0
- package/index.ts +106 -0
- package/learning.ts +779 -0
- package/package.json +51 -0
- package/planning.ts +1028 -0
- package/reasoning.ts +1019 -0
- package/search-discovery.ts +654 -0
- package/tsconfig.json +25 -0
- package/types.ts +674 -0
package/planning.ts
ADDED
|
@@ -0,0 +1,1028 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planning Module
|
|
3
|
+
* Task decomposition, prioritization, resource allocation, and progress tracking
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Plan,
|
|
8
|
+
Task,
|
|
9
|
+
Milestone,
|
|
10
|
+
Resource,
|
|
11
|
+
Timeline,
|
|
12
|
+
Phase,
|
|
13
|
+
ProgressReport,
|
|
14
|
+
Risk,
|
|
15
|
+
Priority,
|
|
16
|
+
TaskStatus,
|
|
17
|
+
ThinkingEvent,
|
|
18
|
+
ThinkingError,
|
|
19
|
+
ThinkingStage
|
|
20
|
+
} from './types';
|
|
21
|
+
import { EventEmitter } from 'events';
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// TASK DECOMPOSITION ENGINE
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
interface DecompositionRule {
|
|
28
|
+
pattern: RegExp;
|
|
29
|
+
subtasks: string[];
|
|
30
|
+
dependencies: string[][];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class TaskDecompositionEngine {
|
|
34
|
+
private decompositionRules: DecompositionRule[] = [
|
|
35
|
+
{
|
|
36
|
+
pattern: /build|create|develop|implement/i,
|
|
37
|
+
subtasks: [
|
|
38
|
+
'Research and requirements gathering',
|
|
39
|
+
'Design and architecture',
|
|
40
|
+
'Implementation',
|
|
41
|
+
'Testing and validation',
|
|
42
|
+
'Deployment'
|
|
43
|
+
],
|
|
44
|
+
dependencies: [[], ['0'], ['1'], ['2'], ['3']]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
pattern: /analyze|research|investigate/i,
|
|
48
|
+
subtasks: [
|
|
49
|
+
'Define scope and objectives',
|
|
50
|
+
'Gather existing data',
|
|
51
|
+
'Conduct analysis',
|
|
52
|
+
'Document findings',
|
|
53
|
+
'Present recommendations'
|
|
54
|
+
],
|
|
55
|
+
dependencies: [[], ['0'], ['1'], ['2'], ['3']]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
pattern: /optimize|improve|enhance/i,
|
|
59
|
+
subtasks: [
|
|
60
|
+
'Current state assessment',
|
|
61
|
+
'Identify improvement areas',
|
|
62
|
+
'Design improvements',
|
|
63
|
+
'Implement changes',
|
|
64
|
+
'Measure results'
|
|
65
|
+
],
|
|
66
|
+
dependencies: [[], ['0'], ['1'], ['2'], ['3']]
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
decompose(goal: string): { subtasks: string[]; dependencies: string[][] } {
|
|
71
|
+
// Find matching decomposition rule
|
|
72
|
+
for (const rule of this.decompositionRules) {
|
|
73
|
+
if (rule.pattern.test(goal)) {
|
|
74
|
+
return {
|
|
75
|
+
subtasks: rule.subtasks,
|
|
76
|
+
dependencies: rule.dependencies
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Default decomposition
|
|
82
|
+
return {
|
|
83
|
+
subtasks: [
|
|
84
|
+
'Understand requirements',
|
|
85
|
+
'Plan approach',
|
|
86
|
+
'Execute plan',
|
|
87
|
+
'Review and validate'
|
|
88
|
+
],
|
|
89
|
+
dependencies: [[], ['0'], ['1'], ['2']]
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
estimateComplexity(task: string): number {
|
|
94
|
+
// Estimate complexity based on keywords
|
|
95
|
+
const complexityIndicators = [
|
|
96
|
+
{ keywords: ['complex', 'difficult', 'challenging', 'advanced'], weight: 0.9 },
|
|
97
|
+
{ keywords: ['simple', 'easy', 'basic', 'straightforward'], weight: 0.3 },
|
|
98
|
+
{ keywords: ['research', 'analysis', 'investigation'], weight: 0.7 },
|
|
99
|
+
{ keywords: ['implement', 'build', 'develop'], weight: 0.8 }
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const taskLower = task.toLowerCase();
|
|
103
|
+
let complexity = 0.5; // Default
|
|
104
|
+
|
|
105
|
+
complexityIndicators.forEach(indicator => {
|
|
106
|
+
if (indicator.keywords.some(kw => taskLower.includes(kw))) {
|
|
107
|
+
complexity = indicator.weight;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Adjust based on task length (longer tasks tend to be more complex)
|
|
112
|
+
const wordCount = task.split(/\s+/).length;
|
|
113
|
+
complexity += Math.min(0.2, wordCount * 0.01);
|
|
114
|
+
|
|
115
|
+
return Math.min(1, complexity);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
estimateDuration(task: string, complexity: number): number {
|
|
119
|
+
// Duration in minutes
|
|
120
|
+
const baseDuration = 30;
|
|
121
|
+
const complexityMultiplier = 1 + complexity * 4; // 1x to 5x
|
|
122
|
+
|
|
123
|
+
return Math.round(baseDuration * complexityMultiplier);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// PRIORITIZATION ENGINE
|
|
129
|
+
// ============================================================================
|
|
130
|
+
|
|
131
|
+
interface PrioritizationCriteria {
|
|
132
|
+
urgency: number;
|
|
133
|
+
importance: number;
|
|
134
|
+
effort: number;
|
|
135
|
+
risk: number;
|
|
136
|
+
dependencies: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
class PrioritizationEngine {
|
|
140
|
+
private weights = {
|
|
141
|
+
urgency: 0.25,
|
|
142
|
+
importance: 0.3,
|
|
143
|
+
effort: 0.2,
|
|
144
|
+
risk: 0.15,
|
|
145
|
+
dependencies: 0.1
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
prioritize(tasks: Task[]): Task[] {
|
|
149
|
+
const scoredTasks = tasks.map(task => ({
|
|
150
|
+
task,
|
|
151
|
+
score: this.calculatePriorityScore(task)
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
return scoredTasks
|
|
155
|
+
.sort((a, b) => b.score - a.score)
|
|
156
|
+
.map(({ task }) => task);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private calculatePriorityScore(task: Task): number {
|
|
160
|
+
const criteria = this.assessCriteria(task);
|
|
161
|
+
|
|
162
|
+
// Calculate weighted score
|
|
163
|
+
let score = 0;
|
|
164
|
+
score += criteria.urgency * this.weights.urgency;
|
|
165
|
+
score += criteria.importance * this.weights.importance;
|
|
166
|
+
score += (1 - criteria.effort) * this.weights.effort; // Lower effort = higher priority
|
|
167
|
+
score += (1 - criteria.risk) * this.weights.risk; // Lower risk = higher priority
|
|
168
|
+
score += (1 - criteria.dependencies) * this.weights.dependencies; // Fewer deps = higher priority
|
|
169
|
+
|
|
170
|
+
return score;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private assessCriteria(task: Task): PrioritizationCriteria {
|
|
174
|
+
return {
|
|
175
|
+
urgency: this.assessUrgency(task),
|
|
176
|
+
importance: this.assessImportance(task),
|
|
177
|
+
effort: this.assessEffort(task),
|
|
178
|
+
risk: this.assessRisk(task),
|
|
179
|
+
dependencies: this.assessDependencies(task)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private assessUrgency(task: Task): number {
|
|
184
|
+
const urgencyKeywords = ['urgent', 'asap', 'immediate', 'critical', 'deadline'];
|
|
185
|
+
const taskText = `${task.title} ${task.description}`.toLowerCase();
|
|
186
|
+
|
|
187
|
+
if (task.priority === Priority.CRITICAL) return 1;
|
|
188
|
+
if (task.priority === Priority.HIGH) return 0.8;
|
|
189
|
+
|
|
190
|
+
const matches = urgencyKeywords.filter(kw => taskText.includes(kw)).length;
|
|
191
|
+
return Math.min(1, matches * 0.3 + (task.priority === Priority.MEDIUM ? 0.3 : task.priority === Priority.LOW ? 0.2 : 0.1));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private assessImportance(task: Task): number {
|
|
195
|
+
const importanceKeywords = ['essential', 'crucial', 'key', 'important', 'core'];
|
|
196
|
+
const taskText = `${task.title} ${task.description}`.toLowerCase();
|
|
197
|
+
|
|
198
|
+
if (task.tags.includes('critical')) return 1;
|
|
199
|
+
|
|
200
|
+
const matches = importanceKeywords.filter(kw => taskText.includes(kw)).length;
|
|
201
|
+
return Math.min(1, matches * 0.25 + 0.3);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private assessEffort(task: Task): number {
|
|
205
|
+
// Normalize estimated duration (assuming max 8 hours)
|
|
206
|
+
return Math.min(1, task.estimatedDuration / 480);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private assessRisk(task: Task): number {
|
|
210
|
+
const riskKeywords = ['risk', 'uncertain', 'unknown', 'complex', 'difficult'];
|
|
211
|
+
const taskText = `${task.title} ${task.description}`.toLowerCase();
|
|
212
|
+
|
|
213
|
+
const matches = riskKeywords.filter(kw => taskText.includes(kw)).length;
|
|
214
|
+
return Math.min(1, matches * 0.3);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private assessDependencies(task: Task): number {
|
|
218
|
+
return Math.min(1, task.dependencies.length / 5);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Calculate critical path
|
|
223
|
+
*/
|
|
224
|
+
calculateCriticalPath(tasks: Task[]): string[] {
|
|
225
|
+
const taskMap = new Map(tasks.map(t => [t.id, t]));
|
|
226
|
+
const visited = new Set<string>();
|
|
227
|
+
const path: string[] = [];
|
|
228
|
+
|
|
229
|
+
const visit = (taskId: string, currentPath: string[] = []): number => {
|
|
230
|
+
if (visited.has(taskId)) return 0;
|
|
231
|
+
if (currentPath.includes(taskId)) return 0; // Cycle detected
|
|
232
|
+
|
|
233
|
+
const task = taskMap.get(taskId);
|
|
234
|
+
if (!task) return 0;
|
|
235
|
+
|
|
236
|
+
let maxPathDuration = task.estimatedDuration;
|
|
237
|
+
|
|
238
|
+
for (const depId of task.dependencies) {
|
|
239
|
+
const depDuration = visit(depId, [...currentPath, taskId]);
|
|
240
|
+
maxPathDuration = Math.max(maxPathDuration, task.estimatedDuration + depDuration);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
visited.add(taskId);
|
|
244
|
+
path.push(taskId);
|
|
245
|
+
|
|
246
|
+
return maxPathDuration;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Find the longest path
|
|
250
|
+
let maxDuration = 0;
|
|
251
|
+
let criticalPath: string[] = [];
|
|
252
|
+
|
|
253
|
+
for (const task of tasks) {
|
|
254
|
+
visited.clear();
|
|
255
|
+
path.length = 0;
|
|
256
|
+
const duration = visit(task.id);
|
|
257
|
+
|
|
258
|
+
if (duration > maxDuration) {
|
|
259
|
+
maxDuration = duration;
|
|
260
|
+
criticalPath = [...path];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return criticalPath;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ============================================================================
|
|
269
|
+
// RESOURCE ALLOCATOR
|
|
270
|
+
// ============================================================================
|
|
271
|
+
|
|
272
|
+
class ResourceAllocator {
|
|
273
|
+
private resources: Map<string, Resource> = new Map();
|
|
274
|
+
|
|
275
|
+
addResource(resource: Resource): void {
|
|
276
|
+
this.resources.set(resource.id, resource);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
allocate(tasks: Task[]): Map<string, string[]> {
|
|
280
|
+
const allocation = new Map<string, string[]>();
|
|
281
|
+
const resourceAvailability = new Map<string, number>();
|
|
282
|
+
|
|
283
|
+
// Initialize availability
|
|
284
|
+
this.resources.forEach((resource, id) => {
|
|
285
|
+
resourceAvailability.set(id, resource.availability);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Allocate resources to tasks
|
|
289
|
+
for (const task of tasks) {
|
|
290
|
+
const suitableResources = this.findSuitableResources(task);
|
|
291
|
+
const allocated: string[] = [];
|
|
292
|
+
|
|
293
|
+
for (const resource of suitableResources) {
|
|
294
|
+
const available = resourceAvailability.get(resource.id) || 0;
|
|
295
|
+
if (available > 0) {
|
|
296
|
+
allocated.push(resource.id);
|
|
297
|
+
resourceAvailability.set(resource.id, available - 1);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
allocation.set(task.id, allocated);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return allocation;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private findSuitableResources(task: Task): Resource[] {
|
|
308
|
+
return Array.from(this.resources.values())
|
|
309
|
+
.filter(resource => {
|
|
310
|
+
// Check if resource skills match task requirements
|
|
311
|
+
if (task.tags.length > 0 && resource.skills) {
|
|
312
|
+
return task.tags.some(tag =>
|
|
313
|
+
resource.skills!.some(skill =>
|
|
314
|
+
skill.toLowerCase().includes(tag.toLowerCase()) ||
|
|
315
|
+
tag.toLowerCase().includes(skill.toLowerCase())
|
|
316
|
+
)
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
})
|
|
321
|
+
.sort((a, b) => b.availability - a.availability);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
checkOverallocation(): Array<{ resourceId: string; overallocation: number }> {
|
|
325
|
+
const overallocations: Array<{ resourceId: string; overallocation: number }> = [];
|
|
326
|
+
|
|
327
|
+
this.resources.forEach((resource, id) => {
|
|
328
|
+
if (resource.availability < 0) {
|
|
329
|
+
overallocations.push({
|
|
330
|
+
resourceId: id,
|
|
331
|
+
overallocation: Math.abs(resource.availability)
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return overallocations;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
getResourceUtilization(): Map<string, number> {
|
|
340
|
+
const utilization = new Map<string, number>();
|
|
341
|
+
|
|
342
|
+
this.resources.forEach((resource, id) => {
|
|
343
|
+
// Calculate utilization (simplified)
|
|
344
|
+
utilization.set(id, 1 - resource.availability);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return utilization;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ============================================================================
|
|
352
|
+
// PROGRESS TRACKER
|
|
353
|
+
// ============================================================================
|
|
354
|
+
|
|
355
|
+
class ProgressTracker {
|
|
356
|
+
private taskHistory: Map<string, Array<{ timestamp: Date; status: TaskStatus; progress: number }>> = new Map();
|
|
357
|
+
|
|
358
|
+
recordProgress(taskId: string, status: TaskStatus, progress: number): void {
|
|
359
|
+
if (!this.taskHistory.has(taskId)) {
|
|
360
|
+
this.taskHistory.set(taskId, []);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
this.taskHistory.get(taskId)!.push({
|
|
364
|
+
timestamp: new Date(),
|
|
365
|
+
status,
|
|
366
|
+
progress: Math.min(100, Math.max(0, progress))
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
getProgress(taskId: string): number {
|
|
371
|
+
const history = this.taskHistory.get(taskId);
|
|
372
|
+
if (!history || history.length === 0) return 0;
|
|
373
|
+
return history[history.length - 1].progress;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
calculateVelocity(taskId: string): number {
|
|
377
|
+
const history = this.taskHistory.get(taskId);
|
|
378
|
+
if (!history || history.length < 2) return 0;
|
|
379
|
+
|
|
380
|
+
const first = history[0];
|
|
381
|
+
const last = history[history.length - 1];
|
|
382
|
+
const timeDiff = last.timestamp.getTime() - first.timestamp.getTime();
|
|
383
|
+
const progressDiff = last.progress - first.progress;
|
|
384
|
+
|
|
385
|
+
return timeDiff > 0 ? (progressDiff / timeDiff) * 3600000 : 0; // Progress per hour
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
predictCompletion(taskId: string, totalWork: number): Date | null {
|
|
389
|
+
const velocity = this.calculateVelocity(taskId);
|
|
390
|
+
const currentProgress = this.getProgress(taskId);
|
|
391
|
+
|
|
392
|
+
if (velocity <= 0 || currentProgress >= 100) return null;
|
|
393
|
+
|
|
394
|
+
const remainingWork = totalWork * (1 - currentProgress / 100);
|
|
395
|
+
const hoursRemaining = remainingWork / velocity;
|
|
396
|
+
|
|
397
|
+
return new Date(Date.now() + hoursRemaining * 3600000);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
getProgressTrend(taskId: string): 'improving' | 'stable' | 'declining' {
|
|
401
|
+
const history = this.taskHistory.get(taskId);
|
|
402
|
+
if (!history || history.length < 3) return 'stable';
|
|
403
|
+
|
|
404
|
+
const recent = history.slice(-3);
|
|
405
|
+
const velocities: number[] = [];
|
|
406
|
+
|
|
407
|
+
for (let i = 1; i < recent.length; i++) {
|
|
408
|
+
const timeDiff = recent[i].timestamp.getTime() - recent[i - 1].timestamp.getTime();
|
|
409
|
+
const progressDiff = recent[i].progress - recent[i - 1].progress;
|
|
410
|
+
velocities.push(timeDiff > 0 ? progressDiff / timeDiff : 0);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const avgVelocity = velocities.reduce((a, b) => a + b, 0) / velocities.length;
|
|
414
|
+
const firstVelocity = velocities[0];
|
|
415
|
+
|
|
416
|
+
if (avgVelocity > firstVelocity * 1.2) return 'improving';
|
|
417
|
+
if (avgVelocity < firstVelocity * 0.8) return 'declining';
|
|
418
|
+
return 'stable';
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ============================================================================
|
|
423
|
+
// RISK ANALYZER
|
|
424
|
+
// ============================================================================
|
|
425
|
+
|
|
426
|
+
class RiskAnalyzer {
|
|
427
|
+
private knownRisks: Map<string, Risk> = new Map();
|
|
428
|
+
|
|
429
|
+
analyzeRisks(tasks: Task[], resources: Resource[]): Risk[] {
|
|
430
|
+
const risks: Risk[] = [];
|
|
431
|
+
|
|
432
|
+
// Analyze task-related risks
|
|
433
|
+
tasks.forEach(task => {
|
|
434
|
+
const taskRisks = this.identifyTaskRisks(task);
|
|
435
|
+
risks.push(...taskRisks);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Analyze resource-related risks
|
|
439
|
+
const resourceRisks = this.identifyResourceRisks(tasks, resources);
|
|
440
|
+
risks.push(...resourceRisks);
|
|
441
|
+
|
|
442
|
+
// Analyze schedule-related risks
|
|
443
|
+
const scheduleRisks = this.identifyScheduleRisks(tasks);
|
|
444
|
+
risks.push(...scheduleRisks);
|
|
445
|
+
|
|
446
|
+
// Sort by risk score (probability * impact)
|
|
447
|
+
return risks
|
|
448
|
+
.map(risk => ({ ...risk, riskScore: risk.probability * risk.impact }))
|
|
449
|
+
.sort((a, b) => (b as any).riskScore - (a as any).riskScore);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private identifyTaskRisks(task: Task): Risk[] {
|
|
453
|
+
const risks: Risk[] = [];
|
|
454
|
+
|
|
455
|
+
// Complexity risk
|
|
456
|
+
if (task.estimatedDuration > 240) { // > 4 hours
|
|
457
|
+
risks.push({
|
|
458
|
+
id: `risk_complexity_${task.id}`,
|
|
459
|
+
description: `Task "${task.title}" is complex and may take longer than estimated`,
|
|
460
|
+
probability: 0.6,
|
|
461
|
+
impact: 0.7,
|
|
462
|
+
mitigation: 'Break down into smaller subtasks',
|
|
463
|
+
status: 'identified'
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Dependency risk
|
|
468
|
+
if (task.dependencies.length > 3) {
|
|
469
|
+
risks.push({
|
|
470
|
+
id: `risk_deps_${task.id}`,
|
|
471
|
+
description: `Task "${task.title}" has many dependencies`,
|
|
472
|
+
probability: 0.5,
|
|
473
|
+
impact: 0.6,
|
|
474
|
+
mitigation: 'Monitor dependencies closely and have backup plans',
|
|
475
|
+
status: 'identified'
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Uncertainty risk
|
|
480
|
+
const uncertaintyKeywords = ['unclear', 'unknown', 'uncertain', 'tbd', 'tba'];
|
|
481
|
+
const taskText = `${task.title} ${task.description}`.toLowerCase();
|
|
482
|
+
if (uncertaintyKeywords.some(kw => taskText.includes(kw))) {
|
|
483
|
+
risks.push({
|
|
484
|
+
id: `risk_uncertainty_${task.id}`,
|
|
485
|
+
description: `Task "${task.title}" has unclear requirements`,
|
|
486
|
+
probability: 0.7,
|
|
487
|
+
impact: 0.5,
|
|
488
|
+
mitigation: 'Clarify requirements before starting',
|
|
489
|
+
status: 'identified'
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return risks;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private identifyResourceRisks(tasks: Task[], resources: Resource[]): Risk[] {
|
|
497
|
+
const risks: Risk[] = [];
|
|
498
|
+
|
|
499
|
+
// Resource availability risk
|
|
500
|
+
const totalAvailability = resources.reduce((sum, r) => sum + r.availability, 0);
|
|
501
|
+
const totalWork = tasks.reduce((sum, t) => sum + t.estimatedDuration, 0);
|
|
502
|
+
|
|
503
|
+
if (totalWork > totalAvailability * 480) { // Assuming 8 hours per availability unit
|
|
504
|
+
risks.push({
|
|
505
|
+
id: 'risk_resource_availability',
|
|
506
|
+
description: 'Insufficient resources for planned work',
|
|
507
|
+
probability: 0.8,
|
|
508
|
+
impact: 0.9,
|
|
509
|
+
mitigation: 'Add more resources or extend timeline',
|
|
510
|
+
status: 'identified'
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return risks;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private identifyScheduleRisks(tasks: Task[]): Risk[] {
|
|
518
|
+
const risks: Risk[] = [];
|
|
519
|
+
|
|
520
|
+
// Check for tight schedules
|
|
521
|
+
const criticalTasks = tasks.filter(t => t.priority === Priority.CRITICAL);
|
|
522
|
+
if (criticalTasks.length > tasks.length * 0.3) {
|
|
523
|
+
risks.push({
|
|
524
|
+
id: 'risk_tight_schedule',
|
|
525
|
+
description: 'Too many critical tasks may lead to schedule slip',
|
|
526
|
+
probability: 0.6,
|
|
527
|
+
impact: 0.7,
|
|
528
|
+
mitigation: 'Re-prioritize and stagger critical tasks',
|
|
529
|
+
status: 'identified'
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return risks;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
updateRiskStatus(riskId: string, status: Risk['status']): void {
|
|
537
|
+
const risk = this.knownRisks.get(riskId);
|
|
538
|
+
if (risk) {
|
|
539
|
+
risk.status = status;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ============================================================================
|
|
545
|
+
// MAIN PLANNING ENGINE
|
|
546
|
+
// ============================================================================
|
|
547
|
+
|
|
548
|
+
export class PlanningEngine extends EventEmitter {
|
|
549
|
+
private decompositionEngine: TaskDecompositionEngine;
|
|
550
|
+
private prioritizationEngine: PrioritizationEngine;
|
|
551
|
+
private resourceAllocator: ResourceAllocator;
|
|
552
|
+
private progressTracker: ProgressTracker;
|
|
553
|
+
private riskAnalyzer: RiskAnalyzer;
|
|
554
|
+
private plans: Map<string, Plan> = new Map();
|
|
555
|
+
|
|
556
|
+
constructor() {
|
|
557
|
+
super();
|
|
558
|
+
this.decompositionEngine = new TaskDecompositionEngine();
|
|
559
|
+
this.prioritizationEngine = new PrioritizationEngine();
|
|
560
|
+
this.resourceAllocator = new ResourceAllocator();
|
|
561
|
+
this.progressTracker = new ProgressTracker();
|
|
562
|
+
this.riskAnalyzer = new RiskAnalyzer();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Create a comprehensive plan
|
|
567
|
+
*/
|
|
568
|
+
createPlan(
|
|
569
|
+
goal: string,
|
|
570
|
+
options: {
|
|
571
|
+
resources?: Resource[];
|
|
572
|
+
deadline?: Date;
|
|
573
|
+
constraints?: string[];
|
|
574
|
+
} = {}
|
|
575
|
+
): Plan {
|
|
576
|
+
const planId = this.generateId();
|
|
577
|
+
const createdAt = new Date();
|
|
578
|
+
|
|
579
|
+
this.emit('planning_start', {
|
|
580
|
+
id: planId,
|
|
581
|
+
stage: ThinkingStage.PLANNING,
|
|
582
|
+
timestamp: createdAt,
|
|
583
|
+
data: { goal }
|
|
584
|
+
} as ThinkingEvent);
|
|
585
|
+
|
|
586
|
+
// Decompose goal into tasks
|
|
587
|
+
const { subtasks, dependencies } = this.decompositionEngine.decompose(goal);
|
|
588
|
+
|
|
589
|
+
// Create tasks
|
|
590
|
+
const tasks: Task[] = subtasks.map((description, index) => {
|
|
591
|
+
const complexity = this.decompositionEngine.estimateComplexity(description);
|
|
592
|
+
const duration = this.decompositionEngine.estimateDuration(description, complexity);
|
|
593
|
+
|
|
594
|
+
return {
|
|
595
|
+
id: `task_${planId}_${index}`,
|
|
596
|
+
title: description,
|
|
597
|
+
description,
|
|
598
|
+
priority: this.inferPriority(description, index),
|
|
599
|
+
status: TaskStatus.PENDING,
|
|
600
|
+
estimatedDuration: duration,
|
|
601
|
+
dependencies: dependencies[index].map(i => `task_${planId}_${i}`),
|
|
602
|
+
subtasks: [],
|
|
603
|
+
tags: []
|
|
604
|
+
};
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// Prioritize tasks
|
|
608
|
+
const prioritizedTasks = this.prioritizationEngine.prioritize(tasks);
|
|
609
|
+
|
|
610
|
+
// Add resources if provided
|
|
611
|
+
if (options.resources) {
|
|
612
|
+
options.resources.forEach(r => this.resourceAllocator.addResource(r));
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Allocate resources
|
|
616
|
+
const resourceAllocation = this.resourceAllocator.allocate(prioritizedTasks);
|
|
617
|
+
|
|
618
|
+
// Assign resources to tasks
|
|
619
|
+
prioritizedTasks.forEach(task => {
|
|
620
|
+
const allocated = resourceAllocation.get(task.id) || [];
|
|
621
|
+
if (allocated.length > 0) {
|
|
622
|
+
task.assignedTo = allocated[0];
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// Create milestones
|
|
627
|
+
const milestones = this.createMilestones(prioritizedTasks, goal);
|
|
628
|
+
|
|
629
|
+
// Create timeline
|
|
630
|
+
const timeline = this.createTimeline(prioritizedTasks, milestones, options.deadline);
|
|
631
|
+
|
|
632
|
+
// Analyze risks
|
|
633
|
+
const risks = this.riskAnalyzer.analyzeRisks(
|
|
634
|
+
prioritizedTasks,
|
|
635
|
+
options.resources || []
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
const plan: Plan = {
|
|
639
|
+
id: planId,
|
|
640
|
+
goal,
|
|
641
|
+
tasks: prioritizedTasks,
|
|
642
|
+
milestones,
|
|
643
|
+
resources: options.resources || [],
|
|
644
|
+
timeline,
|
|
645
|
+
status: TaskStatus.PENDING,
|
|
646
|
+
createdAt,
|
|
647
|
+
updatedAt: createdAt
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
this.plans.set(planId, plan);
|
|
651
|
+
|
|
652
|
+
this.emit('planning_complete', {
|
|
653
|
+
id: planId,
|
|
654
|
+
stage: ThinkingStage.PLANNING,
|
|
655
|
+
timestamp: new Date(),
|
|
656
|
+
data: {
|
|
657
|
+
plan,
|
|
658
|
+
taskCount: tasks.length,
|
|
659
|
+
riskCount: risks.length
|
|
660
|
+
}
|
|
661
|
+
} as ThinkingEvent);
|
|
662
|
+
|
|
663
|
+
return plan;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Update task progress
|
|
668
|
+
*/
|
|
669
|
+
updateTaskProgress(
|
|
670
|
+
planId: string,
|
|
671
|
+
taskId: string,
|
|
672
|
+
progress: number,
|
|
673
|
+
status?: TaskStatus
|
|
674
|
+
): void {
|
|
675
|
+
const plan = this.plans.get(planId);
|
|
676
|
+
if (!plan) throw new Error(`Plan ${planId} not found`);
|
|
677
|
+
|
|
678
|
+
const task = plan.tasks.find(t => t.id === taskId);
|
|
679
|
+
if (!task) throw new Error(`Task ${taskId} not found`);
|
|
680
|
+
|
|
681
|
+
const newStatus = status || (progress >= 100 ? TaskStatus.COMPLETED : progress > 0 ? TaskStatus.IN_PROGRESS : TaskStatus.PENDING);
|
|
682
|
+
|
|
683
|
+
task.status = newStatus;
|
|
684
|
+
if (newStatus === TaskStatus.COMPLETED) {
|
|
685
|
+
task.endTime = new Date();
|
|
686
|
+
} else if (newStatus === TaskStatus.IN_PROGRESS && !task.startTime) {
|
|
687
|
+
task.startTime = new Date();
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
this.progressTracker.recordProgress(taskId, newStatus, progress);
|
|
691
|
+
|
|
692
|
+
// Update plan status
|
|
693
|
+
this.updatePlanStatus(plan);
|
|
694
|
+
plan.updatedAt = new Date();
|
|
695
|
+
|
|
696
|
+
this.emit('task_progress', {
|
|
697
|
+
id: taskId,
|
|
698
|
+
stage: ThinkingStage.PLANNING,
|
|
699
|
+
timestamp: new Date(),
|
|
700
|
+
data: { planId, taskId, progress, status: newStatus }
|
|
701
|
+
} as ThinkingEvent);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Generate progress report
|
|
706
|
+
*/
|
|
707
|
+
generateProgressReport(planId: string): ProgressReport {
|
|
708
|
+
const plan = this.plans.get(planId);
|
|
709
|
+
if (!plan) throw new Error(`Plan ${planId} not found`);
|
|
710
|
+
|
|
711
|
+
const completedTasks = plan.tasks.filter(t => t.status === TaskStatus.COMPLETED).length;
|
|
712
|
+
const totalTasks = plan.tasks.length;
|
|
713
|
+
const overallProgress = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0;
|
|
714
|
+
|
|
715
|
+
// Calculate if on track
|
|
716
|
+
const onTrack = this.isOnTrack(plan);
|
|
717
|
+
|
|
718
|
+
// Get risks
|
|
719
|
+
const risks = this.riskAnalyzer.analyzeRisks(plan.tasks, plan.resources);
|
|
720
|
+
|
|
721
|
+
// Determine next actions
|
|
722
|
+
const nextActions = this.determineNextActions(plan);
|
|
723
|
+
|
|
724
|
+
return {
|
|
725
|
+
planId,
|
|
726
|
+
timestamp: new Date(),
|
|
727
|
+
overallProgress,
|
|
728
|
+
completedTasks,
|
|
729
|
+
totalTasks,
|
|
730
|
+
onTrack,
|
|
731
|
+
risks,
|
|
732
|
+
nextActions
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Adjust plan based on changes
|
|
738
|
+
*/
|
|
739
|
+
adjustPlan(
|
|
740
|
+
planId: string,
|
|
741
|
+
changes: {
|
|
742
|
+
addTasks?: Omit<Task, 'id'>[];
|
|
743
|
+
removeTasks?: string[];
|
|
744
|
+
updateTasks?: Partial<Task>[];
|
|
745
|
+
newDeadline?: Date;
|
|
746
|
+
}
|
|
747
|
+
): Plan {
|
|
748
|
+
const plan = this.plans.get(planId);
|
|
749
|
+
if (!plan) throw new Error(`Plan ${planId} not found`);
|
|
750
|
+
|
|
751
|
+
// Add new tasks
|
|
752
|
+
if (changes.addTasks) {
|
|
753
|
+
const newTasks = changes.addTasks.map((task, index) => ({
|
|
754
|
+
...task,
|
|
755
|
+
id: `task_${planId}_new_${Date.now()}_${index}`
|
|
756
|
+
}));
|
|
757
|
+
plan.tasks.push(...newTasks);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Remove tasks
|
|
761
|
+
if (changes.removeTasks) {
|
|
762
|
+
plan.tasks = plan.tasks.filter(t => !changes.removeTasks!.includes(t.id));
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Update tasks
|
|
766
|
+
if (changes.updateTasks) {
|
|
767
|
+
changes.updateTasks.forEach(update => {
|
|
768
|
+
const task = plan.tasks.find(t => t.id === (update as any).id);
|
|
769
|
+
if (task) {
|
|
770
|
+
Object.assign(task, update);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Re-prioritize
|
|
776
|
+
plan.tasks = this.prioritizationEngine.prioritize(plan.tasks);
|
|
777
|
+
|
|
778
|
+
// Update timeline if new deadline
|
|
779
|
+
if (changes.newDeadline) {
|
|
780
|
+
plan.timeline.endDate = changes.newDeadline;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
plan.updatedAt = new Date();
|
|
784
|
+
|
|
785
|
+
this.emit('plan_adjusted', {
|
|
786
|
+
id: planId,
|
|
787
|
+
stage: ThinkingStage.PLANNING,
|
|
788
|
+
timestamp: new Date(),
|
|
789
|
+
data: { changes }
|
|
790
|
+
} as ThinkingEvent);
|
|
791
|
+
|
|
792
|
+
return plan;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Get plan by ID
|
|
797
|
+
*/
|
|
798
|
+
getPlan(planId: string): Plan | undefined {
|
|
799
|
+
return this.plans.get(planId);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Get all plans
|
|
804
|
+
*/
|
|
805
|
+
getAllPlans(): Plan[] {
|
|
806
|
+
return Array.from(this.plans.values());
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Private helper methods
|
|
810
|
+
private generateId(): string {
|
|
811
|
+
return `plan_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
private inferPriority(description: string, index: number): Priority {
|
|
815
|
+
const lower = description.toLowerCase();
|
|
816
|
+
|
|
817
|
+
if (lower.includes('critical') || lower.includes('urgent')) return Priority.CRITICAL;
|
|
818
|
+
if (lower.includes('important') || lower.includes('key')) return Priority.HIGH;
|
|
819
|
+
if (index < 2) return Priority.HIGH; // First tasks are usually important
|
|
820
|
+
if (lower.includes('optional') || lower.includes('nice to have')) return Priority.LOW;
|
|
821
|
+
|
|
822
|
+
return Priority.MEDIUM;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
private createMilestones(tasks: Task[], goal: string): Milestone[] {
|
|
826
|
+
const milestones: Milestone[] = [];
|
|
827
|
+
const totalTasks = tasks.length;
|
|
828
|
+
|
|
829
|
+
if (totalTasks >= 3) {
|
|
830
|
+
milestones.push({
|
|
831
|
+
id: `milestone_${Date.now()}_1`,
|
|
832
|
+
title: 'Phase 1 Complete',
|
|
833
|
+
description: 'Initial phase completed',
|
|
834
|
+
criteria: [`Complete ${Math.ceil(totalTasks * 0.25)} tasks`],
|
|
835
|
+
status: TaskStatus.PENDING
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
if (totalTasks >= 5) {
|
|
840
|
+
milestones.push({
|
|
841
|
+
id: `milestone_${Date.now()}_2`,
|
|
842
|
+
title: 'Halfway Point',
|
|
843
|
+
description: '50% of tasks completed',
|
|
844
|
+
criteria: [`Complete ${Math.ceil(totalTasks * 0.5)} tasks`],
|
|
845
|
+
status: TaskStatus.PENDING
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
milestones.push({
|
|
850
|
+
id: `milestone_${Date.now()}_final`,
|
|
851
|
+
title: 'Goal Achieved',
|
|
852
|
+
description: goal,
|
|
853
|
+
criteria: ['All tasks completed'],
|
|
854
|
+
status: TaskStatus.PENDING
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
return milestones;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
private createTimeline(
|
|
861
|
+
tasks: Task[],
|
|
862
|
+
milestones: Milestone[],
|
|
863
|
+
deadline?: Date
|
|
864
|
+
): Timeline {
|
|
865
|
+
const startDate = new Date();
|
|
866
|
+
const criticalPath = this.prioritizationEngine.calculateCriticalPath(tasks);
|
|
867
|
+
|
|
868
|
+
// Calculate end date based on tasks
|
|
869
|
+
const totalDuration = tasks.reduce((sum, t) => sum + t.estimatedDuration, 0);
|
|
870
|
+
const calculatedEndDate = new Date(startDate.getTime() + totalDuration * 60000);
|
|
871
|
+
|
|
872
|
+
const endDate = deadline || calculatedEndDate;
|
|
873
|
+
|
|
874
|
+
// Create phases
|
|
875
|
+
const phases = this.createPhases(tasks);
|
|
876
|
+
|
|
877
|
+
return {
|
|
878
|
+
startDate,
|
|
879
|
+
endDate,
|
|
880
|
+
phases,
|
|
881
|
+
criticalPath,
|
|
882
|
+
buffer: deadline ?
|
|
883
|
+
(deadline.getTime() - calculatedEndDate.getTime()) / 60000 :
|
|
884
|
+
totalDuration * 0.2 // 20% buffer
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
private createPhases(tasks: Task[]): Phase[] {
|
|
889
|
+
const phases: Phase[] = [];
|
|
890
|
+
const phaseNames = ['Planning', 'Execution', 'Review'];
|
|
891
|
+
const tasksPerPhase = Math.ceil(tasks.length / phaseNames.length);
|
|
892
|
+
|
|
893
|
+
let currentDate = new Date();
|
|
894
|
+
|
|
895
|
+
phaseNames.forEach((name, index) => {
|
|
896
|
+
const phaseTasks = tasks.slice(index * tasksPerPhase, (index + 1) * tasksPerPhase);
|
|
897
|
+
const phaseDuration = phaseTasks.reduce((sum, t) => sum + t.estimatedDuration, 0);
|
|
898
|
+
|
|
899
|
+
const phase: Phase = {
|
|
900
|
+
id: `phase_${Date.now()}_${index}`,
|
|
901
|
+
name,
|
|
902
|
+
startDate: new Date(currentDate),
|
|
903
|
+
endDate: new Date(currentDate.getTime() + phaseDuration * 60000),
|
|
904
|
+
tasks: phaseTasks.map(t => t.id),
|
|
905
|
+
dependencies: index > 0 ? [`phase_${Date.now()}_${index - 1}`] : []
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
phases.push(phase);
|
|
909
|
+
currentDate = phase.endDate;
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
return phases;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
private updatePlanStatus(plan: Plan): void {
|
|
916
|
+
const completed = plan.tasks.filter(t => t.status === TaskStatus.COMPLETED).length;
|
|
917
|
+
const inProgress = plan.tasks.filter(t => t.status === TaskStatus.IN_PROGRESS).length;
|
|
918
|
+
const failed = plan.tasks.filter(t => t.status === TaskStatus.FAILED).length;
|
|
919
|
+
|
|
920
|
+
if (failed > 0 && failed > plan.tasks.length * 0.3) {
|
|
921
|
+
plan.status = TaskStatus.FAILED;
|
|
922
|
+
} else if (completed === plan.tasks.length) {
|
|
923
|
+
plan.status = TaskStatus.COMPLETED;
|
|
924
|
+
} else if (inProgress > 0 || completed > 0) {
|
|
925
|
+
plan.status = TaskStatus.IN_PROGRESS;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
private isOnTrack(plan: Plan): boolean {
|
|
930
|
+
if (!plan.timeline.endDate) return true;
|
|
931
|
+
|
|
932
|
+
const elapsed = Date.now() - plan.timeline.startDate.getTime();
|
|
933
|
+
const total = plan.timeline.endDate.getTime() - plan.timeline.startDate.getTime();
|
|
934
|
+
const expectedProgress = elapsed / total;
|
|
935
|
+
|
|
936
|
+
const completedTasks = plan.tasks.filter(t => t.status === TaskStatus.COMPLETED).length;
|
|
937
|
+
const actualProgress = completedTasks / plan.tasks.length;
|
|
938
|
+
|
|
939
|
+
return actualProgress >= expectedProgress * 0.8; // Allow 20% slack
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
private determineNextActions(plan: Plan): string[] {
|
|
943
|
+
const actions: string[] = [];
|
|
944
|
+
|
|
945
|
+
// Find pending tasks with no incomplete dependencies
|
|
946
|
+
const readyTasks = plan.tasks.filter(t => {
|
|
947
|
+
if (t.status !== TaskStatus.PENDING) return false;
|
|
948
|
+
return t.dependencies.every(depId =>
|
|
949
|
+
plan.tasks.find(t => t.id === depId)?.status === TaskStatus.COMPLETED
|
|
950
|
+
);
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
if (readyTasks.length > 0) {
|
|
954
|
+
actions.push(`Start task: ${readyTasks[0].title}`);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Check for at-risk tasks
|
|
958
|
+
const inProgressTasks = plan.tasks.filter(t => t.status === TaskStatus.IN_PROGRESS);
|
|
959
|
+
inProgressTasks.forEach(task => {
|
|
960
|
+
const trend = this.progressTracker.getProgressTrend(task.id);
|
|
961
|
+
if (trend === 'declining') {
|
|
962
|
+
actions.push(`Review task: ${task.title} (progress declining)`);
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
return actions;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// ============================================================================
|
|
971
|
+
// EXPORT SINGLETON INSTANCE
|
|
972
|
+
// ============================================================================
|
|
973
|
+
|
|
974
|
+
export const planningEngine = new PlanningEngine();
|
|
975
|
+
|
|
976
|
+
// ============================================================================
|
|
977
|
+
// EXAMPLE USAGE
|
|
978
|
+
// ============================================================================
|
|
979
|
+
|
|
980
|
+
/*
|
|
981
|
+
// Create a plan
|
|
982
|
+
const plan = planningEngine.createPlan(
|
|
983
|
+
'Build a scalable microservices architecture with AI capabilities',
|
|
984
|
+
{
|
|
985
|
+
resources: [
|
|
986
|
+
{
|
|
987
|
+
id: 'dev1',
|
|
988
|
+
type: 'human',
|
|
989
|
+
name: 'Senior Developer',
|
|
990
|
+
availability: 0.8,
|
|
991
|
+
skills: ['backend', 'microservices', 'typescript']
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
id: 'dev2',
|
|
995
|
+
type: 'human',
|
|
996
|
+
name: 'ML Engineer',
|
|
997
|
+
availability: 0.6,
|
|
998
|
+
skills: ['machine_learning', 'python', 'tensorflow']
|
|
999
|
+
}
|
|
1000
|
+
],
|
|
1001
|
+
deadline: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30 days
|
|
1002
|
+
}
|
|
1003
|
+
);
|
|
1004
|
+
|
|
1005
|
+
// Update task progress
|
|
1006
|
+
planningEngine.updateTaskProgress(plan.id, plan.tasks[0].id, 50, TaskStatus.IN_PROGRESS);
|
|
1007
|
+
planningEngine.updateTaskProgress(plan.id, plan.tasks[0].id, 100, TaskStatus.COMPLETED);
|
|
1008
|
+
|
|
1009
|
+
// Generate progress report
|
|
1010
|
+
const report = planningEngine.generateProgressReport(plan.id);
|
|
1011
|
+
console.log(`Progress: ${report.overallProgress}%`);
|
|
1012
|
+
console.log(`On track: ${report.onTrack}`);
|
|
1013
|
+
console.log(`Next actions: ${report.nextActions.join(', ')}`);
|
|
1014
|
+
|
|
1015
|
+
// Adjust plan
|
|
1016
|
+
const adjustedPlan = planningEngine.adjustPlan(plan.id, {
|
|
1017
|
+
addTasks: [{
|
|
1018
|
+
title: 'Security audit',
|
|
1019
|
+
description: 'Perform security review',
|
|
1020
|
+
priority: Priority.HIGH,
|
|
1021
|
+
status: TaskStatus.PENDING,
|
|
1022
|
+
estimatedDuration: 120,
|
|
1023
|
+
dependencies: [],
|
|
1024
|
+
subtasks: [],
|
|
1025
|
+
tags: ['security']
|
|
1026
|
+
}]
|
|
1027
|
+
});
|
|
1028
|
+
*/
|