@critical-path/core 0.1.2 → 0.2.1
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/CHANGELOG.md +19 -0
- package/README.md +43 -26
- package/dist/engine/index.d.ts +19 -1
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +129 -6
- package/dist/engine/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +96 -1
- package/dist/index.test.js.map +1 -1
- package/dist/store/firebase.d.ts +113 -0
- package/dist/store/firebase.d.ts.map +1 -0
- package/dist/store/firebase.js +319 -0
- package/dist/store/firebase.js.map +1 -0
- package/dist/store/firebase.test.d.ts +2 -0
- package/dist/store/firebase.test.d.ts.map +1 -0
- package/dist/store/firebase.test.js +45 -0
- package/dist/store/firebase.test.js.map +1 -0
- package/dist/store/index.d.ts +36 -8
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +78 -12
- package/dist/store/index.js.map +1 -1
- package/dist/store/sqlite.d.ts +61 -0
- package/dist/store/sqlite.d.ts.map +1 -0
- package/dist/store/sqlite.js +492 -0
- package/dist/store/sqlite.js.map +1 -0
- package/dist/store/sqlite.test.d.ts +2 -0
- package/dist/store/sqlite.test.d.ts.map +1 -0
- package/dist/store/sqlite.test.js +63 -0
- package/dist/store/sqlite.test.js.map +1 -0
- package/dist/types/index.d.ts +68 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/status.d.ts +14 -0
- package/dist/utils/status.d.ts.map +1 -0
- package/dist/utils/status.js +58 -0
- package/dist/utils/status.js.map +1 -0
- package/package.json +9 -1
- package/src/engine/index.ts +161 -8
- package/src/index.test.ts +113 -1
- package/src/index.ts +1 -0
- package/src/store/firebase.test.ts +59 -0
- package/src/store/firebase.ts +381 -0
- package/src/store/index.ts +113 -19
- package/src/store/sqlite.test.ts +78 -0
- package/src/store/sqlite.ts +729 -0
- package/src/types/index.ts +74 -9
- package/src/utils/status.ts +85 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
export type
|
|
2
|
-
export type
|
|
1
|
+
export type DefaultPriority = 'urgent' | 'high' | 'medium' | 'low' | 'none';
|
|
2
|
+
export type Priority = DefaultPriority | (string & {});
|
|
3
|
+
export type DefaultTaskStatus = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'canceled';
|
|
4
|
+
export type TaskStatus = DefaultTaskStatus | (string & {});
|
|
5
|
+
export type CompletionState = 'done' | 'not_done';
|
|
6
|
+
export type ExecutionState = 'active' | 'inactive';
|
|
7
|
+
export interface StatusDefinition {
|
|
8
|
+
key: string;
|
|
9
|
+
label: string;
|
|
10
|
+
completionState: CompletionState;
|
|
11
|
+
executionState: ExecutionState;
|
|
12
|
+
isCancelled?: boolean;
|
|
13
|
+
}
|
|
3
14
|
export type Role = 'admin' | 'project_manager' | 'contributor' | 'viewer';
|
|
4
15
|
export interface User {
|
|
5
16
|
id: string;
|
|
@@ -9,6 +20,15 @@ export interface User {
|
|
|
9
20
|
role: Role;
|
|
10
21
|
createdAt: string;
|
|
11
22
|
}
|
|
23
|
+
export interface Team {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
leaderId?: string;
|
|
28
|
+
memberIds: string[];
|
|
29
|
+
createdAt: string;
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
}
|
|
12
32
|
export interface CustomFieldDefinition {
|
|
13
33
|
id: string;
|
|
14
34
|
key: string;
|
|
@@ -25,16 +45,40 @@ export interface Project {
|
|
|
25
45
|
description?: string;
|
|
26
46
|
ownerId?: string;
|
|
27
47
|
members?: string[];
|
|
48
|
+
teamIds?: string[];
|
|
49
|
+
statusDefinitions?: StatusDefinition[];
|
|
50
|
+
priorityDefinitions?: Array<{
|
|
51
|
+
key: string;
|
|
52
|
+
label: string;
|
|
53
|
+
level?: number;
|
|
54
|
+
}>;
|
|
28
55
|
customFieldDefinitions?: CustomFieldDefinition[];
|
|
29
56
|
createdAt: string;
|
|
30
57
|
updatedAt: string;
|
|
31
58
|
}
|
|
59
|
+
export interface TaskContainer {
|
|
60
|
+
id: string;
|
|
61
|
+
projectId: string;
|
|
62
|
+
name: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
parentId?: string;
|
|
65
|
+
type?: 'epic' | 'group' | 'section' | 'folder' | string;
|
|
66
|
+
color?: string;
|
|
67
|
+
createdAt: string;
|
|
68
|
+
updatedAt: string;
|
|
69
|
+
}
|
|
32
70
|
export interface TaskDependency {
|
|
33
71
|
id: string;
|
|
34
72
|
taskId: string;
|
|
35
73
|
dependsOnTaskId: string;
|
|
36
74
|
type: 'blocking' | 'blocked_by' | 'relates_to';
|
|
37
75
|
}
|
|
76
|
+
export interface TaskDependencyGraph {
|
|
77
|
+
taskId: string;
|
|
78
|
+
upstreamTasks: Task[];
|
|
79
|
+
downstreamTasks: Task[];
|
|
80
|
+
dependencies: TaskDependency[];
|
|
81
|
+
}
|
|
38
82
|
export interface Task {
|
|
39
83
|
id: string;
|
|
40
84
|
projectId: string;
|
|
@@ -44,23 +88,36 @@ export interface Task {
|
|
|
44
88
|
priority: Priority;
|
|
45
89
|
assigneeId?: string;
|
|
46
90
|
reporterId?: string;
|
|
47
|
-
|
|
91
|
+
reviewerId?: string;
|
|
92
|
+
iterationId?: string;
|
|
93
|
+
teamId?: string;
|
|
94
|
+
containerId?: string;
|
|
95
|
+
plannedStartDate?: string;
|
|
96
|
+
actualStartDate?: string;
|
|
97
|
+
actualEndDate?: string;
|
|
48
98
|
dueDate?: string;
|
|
49
99
|
estimatedHours?: number;
|
|
50
100
|
loggedHours?: number;
|
|
101
|
+
actualHours?: number;
|
|
102
|
+
billableHours?: number;
|
|
103
|
+
estimatedDurationMinutes?: number;
|
|
104
|
+
actualDurationMinutes?: number;
|
|
105
|
+
billableDurationMinutes?: number;
|
|
106
|
+
progress?: number;
|
|
51
107
|
tags?: string[];
|
|
52
108
|
customFields?: Record<string, unknown>;
|
|
53
109
|
parentId?: string;
|
|
54
110
|
createdAt: string;
|
|
55
111
|
updatedAt: string;
|
|
56
112
|
}
|
|
57
|
-
export interface
|
|
113
|
+
export interface Iteration {
|
|
58
114
|
id: string;
|
|
59
115
|
projectId: string;
|
|
60
116
|
name: string;
|
|
61
117
|
goal?: string;
|
|
62
|
-
|
|
63
|
-
|
|
118
|
+
type?: 'sprint' | 'cycle' | 'milestone' | string;
|
|
119
|
+
startDate?: string;
|
|
120
|
+
endDate?: string;
|
|
64
121
|
status: 'planning' | 'active' | 'completed';
|
|
65
122
|
createdAt: string;
|
|
66
123
|
}
|
|
@@ -77,6 +134,7 @@ export interface TimeEntry {
|
|
|
77
134
|
taskId: string;
|
|
78
135
|
userId: string;
|
|
79
136
|
hours: number;
|
|
137
|
+
isBillable?: boolean;
|
|
80
138
|
description?: string;
|
|
81
139
|
loggedAt: string;
|
|
82
140
|
}
|
|
@@ -98,7 +156,7 @@ export interface Webhook {
|
|
|
98
156
|
active: boolean;
|
|
99
157
|
createdAt: string;
|
|
100
158
|
}
|
|
101
|
-
export type WebhookEvent = 'project.created' | 'project.updated' | 'task.created' | 'task.updated' | 'task.deleted' | 'task.status_changed' | 'comment.created' | '
|
|
159
|
+
export type WebhookEvent = 'project.created' | 'project.updated' | 'task.created' | 'task.updated' | 'task.deleted' | 'task.status_changed' | 'comment.created' | 'iteration.started' | 'iteration.completed' | 'team.created' | 'container.created';
|
|
102
160
|
export interface PluginHooks {
|
|
103
161
|
beforeTaskCreate?: (task: Partial<Task>) => Promise<Partial<Task>> | Partial<Task>;
|
|
104
162
|
afterTaskCreate?: (task: Task) => Promise<void> | void;
|
|
@@ -124,7 +182,9 @@ export interface CriticalPathConfig {
|
|
|
124
182
|
projects?: Project[];
|
|
125
183
|
tasks?: Task[];
|
|
126
184
|
users?: User[];
|
|
127
|
-
|
|
185
|
+
iterations?: Iteration[];
|
|
186
|
+
teams?: Team[];
|
|
187
|
+
containers?: TaskContainer[];
|
|
128
188
|
};
|
|
129
189
|
}
|
|
130
190
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Task, StatusDefinition, CompletionState, ExecutionState } from '../types/index.js';
|
|
2
|
+
export declare const DEFAULT_STATUS_DEFINITIONS: Record<string, StatusDefinition>;
|
|
3
|
+
export interface TaskLifecycleState {
|
|
4
|
+
completionState: CompletionState;
|
|
5
|
+
executionState: ExecutionState;
|
|
6
|
+
isDone: boolean;
|
|
7
|
+
isActive: boolean;
|
|
8
|
+
isCancelled: boolean;
|
|
9
|
+
isOverdue: boolean;
|
|
10
|
+
isUpcoming: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveStatusDefinition(statusKey: string, customDefinitions?: StatusDefinition[]): StatusDefinition;
|
|
13
|
+
export declare function deriveTaskLifecycleState(task: Task, customDefinitions?: StatusDefinition[], referenceDate?: Date): TaskLifecycleState;
|
|
14
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/utils/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,gBAAgB,EAChB,eAAe,EACf,cAAc,EACf,MAAM,mBAAmB,CAAC;AAE3B,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAOvE,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,GACrC,gBAAgB,CAkBlB;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,IAAI,EACV,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,EACtC,aAAa,GAAE,IAAiB,GAC/B,kBAAkB,CA+BpB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const DEFAULT_STATUS_DEFINITIONS = {
|
|
2
|
+
backlog: { key: 'backlog', label: 'Backlog', completionState: 'not_done', executionState: 'inactive' },
|
|
3
|
+
todo: { key: 'todo', label: 'To Do', completionState: 'not_done', executionState: 'inactive' },
|
|
4
|
+
in_progress: { key: 'in_progress', label: 'In Progress', completionState: 'not_done', executionState: 'active' },
|
|
5
|
+
in_review: { key: 'in_review', label: 'In Review', completionState: 'not_done', executionState: 'active' },
|
|
6
|
+
done: { key: 'done', label: 'Done', completionState: 'done', executionState: 'inactive' },
|
|
7
|
+
canceled: { key: 'canceled', label: 'Canceled', completionState: 'done', executionState: 'inactive', isCancelled: true }
|
|
8
|
+
};
|
|
9
|
+
export function resolveStatusDefinition(statusKey, customDefinitions) {
|
|
10
|
+
if (customDefinitions) {
|
|
11
|
+
const found = customDefinitions.find((def) => def.key === statusKey);
|
|
12
|
+
if (found)
|
|
13
|
+
return found;
|
|
14
|
+
}
|
|
15
|
+
if (DEFAULT_STATUS_DEFINITIONS[statusKey]) {
|
|
16
|
+
return DEFAULT_STATUS_DEFINITIONS[statusKey];
|
|
17
|
+
}
|
|
18
|
+
// Fallback heuristic for unknown custom statuses:
|
|
19
|
+
const isDoneKey = ['done', 'completed', 'finished', 'closed', 'canceled', 'cancelled'].includes(statusKey.toLowerCase());
|
|
20
|
+
const isActiveKey = ['in_progress', 'active', 'working', 'in_review', 'review'].includes(statusKey.toLowerCase());
|
|
21
|
+
return {
|
|
22
|
+
key: statusKey,
|
|
23
|
+
label: statusKey,
|
|
24
|
+
completionState: isDoneKey ? 'done' : 'not_done',
|
|
25
|
+
executionState: isActiveKey ? 'active' : 'inactive',
|
|
26
|
+
isCancelled: statusKey.toLowerCase().includes('cancel')
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function deriveTaskLifecycleState(task, customDefinitions, referenceDate = new Date()) {
|
|
30
|
+
const def = resolveStatusDefinition(task.status, customDefinitions);
|
|
31
|
+
const isDone = def.completionState === 'done';
|
|
32
|
+
const isActive = def.executionState === 'active';
|
|
33
|
+
const isCancelled = Boolean(def.isCancelled);
|
|
34
|
+
let isOverdue = false;
|
|
35
|
+
if (!isDone && task.dueDate) {
|
|
36
|
+
const due = new Date(task.dueDate);
|
|
37
|
+
if (!isNaN(due.getTime()) && due < referenceDate) {
|
|
38
|
+
isOverdue = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let isUpcoming = false;
|
|
42
|
+
if (!isDone && !isActive && task.plannedStartDate) {
|
|
43
|
+
const plannedStart = new Date(task.plannedStartDate);
|
|
44
|
+
if (!isNaN(plannedStart.getTime()) && plannedStart > referenceDate) {
|
|
45
|
+
isUpcoming = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
completionState: def.completionState,
|
|
50
|
+
executionState: def.executionState,
|
|
51
|
+
isDone,
|
|
52
|
+
isActive,
|
|
53
|
+
isCancelled,
|
|
54
|
+
isOverdue,
|
|
55
|
+
isUpcoming
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/utils/status.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,0BAA0B,GAAqC;IAC1E,OAAO,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,UAAU,EAAE;IACtG,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,UAAU,EAAE;IAC9F,WAAW,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE;IAChH,SAAS,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE;IAC1G,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE;IACzF,QAAQ,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,EAAE;CACzH,CAAC;AAYF,MAAM,UAAU,uBAAuB,CACrC,SAAiB,EACjB,iBAAsC;IAEtC,IAAI,iBAAiB,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC;QACrE,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,IAAI,0BAA0B,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1C,OAAO,0BAA0B,CAAC,SAAS,CAAC,CAAC;IAC/C,CAAC;IACD,kDAAkD;IAClD,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;IACzH,MAAM,WAAW,GAAG,CAAC,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;IAClH,OAAO;QACL,GAAG,EAAE,SAAS;QACd,KAAK,EAAE,SAAS;QAChB,eAAe,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU;QAChD,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU;QACnD,WAAW,EAAE,SAAS,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;KACxD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,IAAU,EACV,iBAAsC,EACtC,gBAAsB,IAAI,IAAI,EAAE;IAEhC,MAAM,GAAG,GAAG,uBAAuB,CAAC,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,GAAG,CAAC,eAAe,KAAK,MAAM,CAAC;IAC9C,MAAM,QAAQ,GAAG,GAAG,CAAC,cAAc,KAAK,QAAQ,CAAC;IACjD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAE7C,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,IAAI,GAAG,GAAG,aAAa,EAAE,CAAC;YACjD,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;IACH,CAAC;IAED,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAClD,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACrD,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,IAAI,YAAY,GAAG,aAAa,EAAE,CAAC;YACnE,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO;QACL,eAAe,EAAE,GAAG,CAAC,eAAe;QACpC,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,MAAM;QACN,QAAQ;QACR,WAAW;QACX,SAAS;QACT,UAAU;KACX,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@critical-path/core",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Core headless domain engine, plugin architecture, and data stores for Critical Path",
|
|
5
5
|
"author": "Jack James",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Pixerate/critical-path.git"
|
|
10
|
+
},
|
|
7
11
|
"type": "module",
|
|
8
12
|
"main": "./dist/index.js",
|
|
9
13
|
"types": "./dist/index.d.ts",
|
|
@@ -14,6 +18,7 @@
|
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
"devDependencies": {
|
|
21
|
+
"@types/better-sqlite3": "^9.6.0",
|
|
17
22
|
"@types/node": "^24.0.0",
|
|
18
23
|
"typescript": "^5.7.0",
|
|
19
24
|
"vitest": "^3.0.0"
|
|
@@ -21,6 +26,9 @@
|
|
|
21
26
|
"engines": {
|
|
22
27
|
"node": ">=24.0.0"
|
|
23
28
|
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"better-sqlite3": "^13.0.3"
|
|
31
|
+
},
|
|
24
32
|
"scripts": {
|
|
25
33
|
"build": "tsc --build",
|
|
26
34
|
"test": "vitest run",
|
package/src/engine/index.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
CriticalPathConfig,
|
|
3
|
+
Project,
|
|
4
|
+
Task,
|
|
5
|
+
TaskDependencyGraph,
|
|
6
|
+
Team,
|
|
7
|
+
TaskContainer,
|
|
8
|
+
Iteration,
|
|
9
|
+
WebhookEvent
|
|
10
|
+
} from '../types/index.js';
|
|
2
11
|
import { StorageAdapter, InMemoryStore } from '../store/index.js';
|
|
3
12
|
import { PluginRegistry } from '../plugins/index.js';
|
|
13
|
+
import { deriveTaskLifecycleState, type TaskLifecycleState } from '../utils/status.js';
|
|
4
14
|
|
|
5
15
|
export class CriticalPathEngine {
|
|
6
16
|
public readonly store: StorageAdapter;
|
|
@@ -30,6 +40,21 @@ export class CriticalPathEngine {
|
|
|
30
40
|
await this.store.createProject(p);
|
|
31
41
|
}
|
|
32
42
|
}
|
|
43
|
+
if (data.teams) {
|
|
44
|
+
for (const tm of data.teams) {
|
|
45
|
+
await this.store.createTeam(tm);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (data.containers) {
|
|
49
|
+
for (const c of data.containers) {
|
|
50
|
+
await this.store.createContainer(c);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (data.iterations) {
|
|
54
|
+
for (const it of data.iterations) {
|
|
55
|
+
await this.store.createIteration(it);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
33
58
|
if (data.tasks) {
|
|
34
59
|
for (const t of data.tasks) {
|
|
35
60
|
await this.store.createTask(t);
|
|
@@ -37,8 +62,7 @@ export class CriticalPathEngine {
|
|
|
37
62
|
}
|
|
38
63
|
}
|
|
39
64
|
|
|
40
|
-
// ---
|
|
41
|
-
|
|
65
|
+
// --- Projects ---
|
|
42
66
|
async getProjects(): Promise<Project[]> {
|
|
43
67
|
return this.store.getProjects();
|
|
44
68
|
}
|
|
@@ -59,6 +83,7 @@ export class CriticalPathEngine {
|
|
|
59
83
|
return created;
|
|
60
84
|
}
|
|
61
85
|
|
|
86
|
+
// --- Tasks ---
|
|
62
87
|
async getTasks(projectId?: string): Promise<Task[]> {
|
|
63
88
|
return this.store.getTasks(projectId);
|
|
64
89
|
}
|
|
@@ -68,7 +93,6 @@ export class CriticalPathEngine {
|
|
|
68
93
|
}
|
|
69
94
|
|
|
70
95
|
async createTask(taskInput: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<Task> {
|
|
71
|
-
// Run plugin beforeTaskCreate hooks
|
|
72
96
|
const processedInput = await this.plugins.runBeforeTaskCreate(taskInput);
|
|
73
97
|
|
|
74
98
|
const created = await this.store.createTask({
|
|
@@ -79,16 +103,27 @@ export class CriticalPathEngine {
|
|
|
79
103
|
priority: processedInput.priority || taskInput.priority || 'medium',
|
|
80
104
|
assigneeId: processedInput.assigneeId ?? taskInput.assigneeId,
|
|
81
105
|
reporterId: processedInput.reporterId ?? taskInput.reporterId,
|
|
82
|
-
|
|
106
|
+
reviewerId: processedInput.reviewerId ?? taskInput.reviewerId,
|
|
107
|
+
iterationId: processedInput.iterationId ?? taskInput.iterationId,
|
|
108
|
+
teamId: processedInput.teamId ?? taskInput.teamId,
|
|
109
|
+
containerId: processedInput.containerId ?? taskInput.containerId,
|
|
110
|
+
plannedStartDate: processedInput.plannedStartDate ?? taskInput.plannedStartDate,
|
|
111
|
+
actualStartDate: processedInput.actualStartDate ?? taskInput.actualStartDate,
|
|
112
|
+
actualEndDate: processedInput.actualEndDate ?? taskInput.actualEndDate,
|
|
83
113
|
dueDate: processedInput.dueDate ?? taskInput.dueDate,
|
|
84
114
|
estimatedHours: processedInput.estimatedHours ?? taskInput.estimatedHours,
|
|
85
115
|
loggedHours: processedInput.loggedHours ?? taskInput.loggedHours ?? 0,
|
|
116
|
+
actualHours: processedInput.actualHours ?? taskInput.actualHours,
|
|
117
|
+
billableHours: processedInput.billableHours ?? taskInput.billableHours,
|
|
118
|
+
estimatedDurationMinutes: processedInput.estimatedDurationMinutes ?? taskInput.estimatedDurationMinutes,
|
|
119
|
+
actualDurationMinutes: processedInput.actualDurationMinutes ?? taskInput.actualDurationMinutes,
|
|
120
|
+
billableDurationMinutes: processedInput.billableDurationMinutes ?? taskInput.billableDurationMinutes,
|
|
121
|
+
progress: processedInput.progress ?? taskInput.progress ?? 0,
|
|
86
122
|
tags: processedInput.tags ?? taskInput.tags ?? [],
|
|
87
123
|
customFields: processedInput.customFields ?? taskInput.customFields ?? {},
|
|
88
124
|
parentId: processedInput.parentId ?? taskInput.parentId
|
|
89
125
|
});
|
|
90
126
|
|
|
91
|
-
// Run plugin afterTaskCreate hooks
|
|
92
127
|
await this.plugins.runAfterTaskCreate(created);
|
|
93
128
|
|
|
94
129
|
await this.store.logActivity({
|
|
@@ -151,18 +186,136 @@ export class CriticalPathEngine {
|
|
|
151
186
|
return deleted;
|
|
152
187
|
}
|
|
153
188
|
|
|
189
|
+
// --- Task Dependencies & Graph ---
|
|
190
|
+
async getTaskDependencyGraph(taskId: string): Promise<TaskDependencyGraph> {
|
|
191
|
+
const dependencies = await this.store.getDependencies(taskId);
|
|
192
|
+
const upstreamTaskIds = new Set<string>();
|
|
193
|
+
const downstreamTaskIds = new Set<string>();
|
|
194
|
+
|
|
195
|
+
for (const dep of dependencies) {
|
|
196
|
+
if (dep.taskId === taskId) {
|
|
197
|
+
upstreamTaskIds.add(dep.dependsOnTaskId);
|
|
198
|
+
} else if (dep.dependsOnTaskId === taskId) {
|
|
199
|
+
downstreamTaskIds.add(dep.taskId);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const upstreamTasks = (
|
|
204
|
+
await Promise.all(Array.from(upstreamTaskIds).map((id) => this.store.getTask(id)))
|
|
205
|
+
).filter((t): t is Task => t !== null);
|
|
206
|
+
|
|
207
|
+
const downstreamTasks = (
|
|
208
|
+
await Promise.all(Array.from(downstreamTaskIds).map((id) => this.store.getTask(id)))
|
|
209
|
+
).filter((t): t is Task => t !== null);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
taskId,
|
|
213
|
+
upstreamTasks,
|
|
214
|
+
downstreamTasks,
|
|
215
|
+
dependencies
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async getTaskLifecycleState(taskId: string): Promise<TaskLifecycleState | null> {
|
|
220
|
+
const task = await this.getTask(taskId);
|
|
221
|
+
if (!task) return null;
|
|
222
|
+
const project = await this.getProject(task.projectId);
|
|
223
|
+
return deriveTaskLifecycleState(task, project?.statusDefinitions);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// --- Teams ---
|
|
227
|
+
async getTeams(): Promise<Team[]> {
|
|
228
|
+
return this.store.getTeams();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async getTeam(id: string): Promise<Team | null> {
|
|
232
|
+
return this.store.getTeam(id);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team> {
|
|
236
|
+
const created = await this.store.createTeam(team);
|
|
237
|
+
this.dispatchWebhook('team.created', { team: created });
|
|
238
|
+
return created;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async updateTeam(id: string, updates: Partial<Team>): Promise<Team | null> {
|
|
242
|
+
return this.store.updateTeam(id, updates);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async deleteTeam(id: string): Promise<boolean> {
|
|
246
|
+
return this.store.deleteTeam(id);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// --- Containers ---
|
|
250
|
+
async getContainers(projectId: string): Promise<TaskContainer[]> {
|
|
251
|
+
return this.store.getContainers(projectId);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async getContainer(id: string): Promise<TaskContainer | null> {
|
|
255
|
+
return this.store.getContainer(id);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
|
|
259
|
+
const created = await this.store.createContainer(container);
|
|
260
|
+
this.dispatchWebhook('container.created', { container: created });
|
|
261
|
+
return created;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null> {
|
|
265
|
+
return this.store.updateContainer(id, updates);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async deleteContainer(id: string): Promise<boolean> {
|
|
269
|
+
return this.store.deleteContainer(id);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// --- Iterations ---
|
|
273
|
+
async getIterations(projectId: string): Promise<Iteration[]> {
|
|
274
|
+
return this.store.getIterations(projectId);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async getIteration(id: string): Promise<Iteration | null> {
|
|
278
|
+
return this.store.getIteration(id);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
|
|
282
|
+
const created = await this.store.createIteration(iteration);
|
|
283
|
+
if (created.status === 'active') {
|
|
284
|
+
this.dispatchWebhook('iteration.started', { iteration: created });
|
|
285
|
+
}
|
|
286
|
+
return created;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null> {
|
|
290
|
+
const existing = await this.store.getIteration(id);
|
|
291
|
+
if (!existing) return null;
|
|
292
|
+
|
|
293
|
+
const updated = await this.store.updateIteration(id, updates);
|
|
294
|
+
if (updated) {
|
|
295
|
+
if (existing.status !== 'active' && updated.status === 'active') {
|
|
296
|
+
this.dispatchWebhook('iteration.started', { iteration: updated });
|
|
297
|
+
} else if (existing.status !== 'completed' && updated.status === 'completed') {
|
|
298
|
+
this.dispatchWebhook('iteration.completed', { iteration: updated });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return updated;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async deleteIteration(id: string): Promise<boolean> {
|
|
305
|
+
return this.store.deleteIteration(id);
|
|
306
|
+
}
|
|
307
|
+
|
|
154
308
|
private async dispatchWebhook(event: WebhookEvent, payload: Record<string, unknown>): Promise<void> {
|
|
155
309
|
const webhooks = await this.store.getWebhooks();
|
|
156
310
|
const active = webhooks.filter((w) => w.active && w.events.includes(event));
|
|
157
311
|
|
|
158
312
|
for (const wh of active) {
|
|
159
|
-
// Fire-and-forget webhook post
|
|
160
313
|
fetch(wh.url, {
|
|
161
314
|
method: 'POST',
|
|
162
315
|
headers: { 'Content-Type': 'application/json' },
|
|
163
316
|
body: JSON.stringify({ event, timestamp: new Date().toISOString(), data: payload })
|
|
164
317
|
}).catch(() => {
|
|
165
|
-
// Silent catch for webhook
|
|
318
|
+
// Silent catch for webhook errors in dev
|
|
166
319
|
});
|
|
167
320
|
}
|
|
168
321
|
}
|
package/src/index.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { CriticalPathEngine } from './
|
|
2
|
+
import { CriticalPathEngine, deriveTaskLifecycleState } from './index.js';
|
|
3
3
|
import type { CriticalPathPlugin } from './types/index.js';
|
|
4
4
|
|
|
5
5
|
describe('CriticalPathEngine Core Tests', () => {
|
|
@@ -60,4 +60,116 @@ describe('CriticalPathEngine Core Tests', () => {
|
|
|
60
60
|
expect(hookExecuted).toBe(true);
|
|
61
61
|
expect(task.tags).toContain('auto-tagged');
|
|
62
62
|
});
|
|
63
|
+
|
|
64
|
+
it('handles teams, containers, and iterations', async () => {
|
|
65
|
+
const engine = new CriticalPathEngine();
|
|
66
|
+
|
|
67
|
+
const team = await engine.createTeam({
|
|
68
|
+
name: 'Engineering',
|
|
69
|
+
memberIds: ['user_1', 'user_2']
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const proj = await engine.createProject({
|
|
73
|
+
key: 'ENG',
|
|
74
|
+
name: 'Engineering Project',
|
|
75
|
+
teamIds: [team.id]
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const container = await engine.createContainer({
|
|
79
|
+
projectId: proj.id,
|
|
80
|
+
name: 'Core Platform Epic',
|
|
81
|
+
type: 'epic'
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const iteration = await engine.createIteration({
|
|
85
|
+
projectId: proj.id,
|
|
86
|
+
name: 'Sprint 1',
|
|
87
|
+
type: 'sprint',
|
|
88
|
+
status: 'active'
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const task = await engine.createTask({
|
|
92
|
+
projectId: proj.id,
|
|
93
|
+
title: 'Setup Database Schema',
|
|
94
|
+
status: 'in_progress',
|
|
95
|
+
priority: 'urgent',
|
|
96
|
+
reviewerId: 'user_2',
|
|
97
|
+
teamId: team.id,
|
|
98
|
+
containerId: container.id,
|
|
99
|
+
iterationId: iteration.id,
|
|
100
|
+
plannedStartDate: '2026-09-01T00:00:00.000Z',
|
|
101
|
+
dueDate: '2026-09-15T00:00:00.000Z',
|
|
102
|
+
estimatedHours: 10,
|
|
103
|
+
actualHours: 8,
|
|
104
|
+
billableHours: 8,
|
|
105
|
+
estimatedDurationMinutes: 600,
|
|
106
|
+
actualDurationMinutes: 480,
|
|
107
|
+
billableDurationMinutes: 480,
|
|
108
|
+
progress: 80
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(task.reviewerId).toBe('user_2');
|
|
112
|
+
expect(task.teamId).toBe(team.id);
|
|
113
|
+
expect(task.containerId).toBe(container.id);
|
|
114
|
+
expect(task.iterationId).toBe(iteration.id);
|
|
115
|
+
expect(task.estimatedHours).toBe(10);
|
|
116
|
+
expect(task.actualHours).toBe(8);
|
|
117
|
+
expect(task.billableHours).toBe(8);
|
|
118
|
+
expect(task.estimatedDurationMinutes).toBe(600);
|
|
119
|
+
expect(task.actualDurationMinutes).toBe(480);
|
|
120
|
+
expect(task.billableDurationMinutes).toBe(480);
|
|
121
|
+
expect(task.progress).toBe(80);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('evaluates status definitions and task lifecycle state', async () => {
|
|
125
|
+
const engine = new CriticalPathEngine();
|
|
126
|
+
|
|
127
|
+
const proj = await engine.createProject({
|
|
128
|
+
key: 'CUST',
|
|
129
|
+
name: 'Custom Workflow Project',
|
|
130
|
+
statusDefinitions: [
|
|
131
|
+
{ key: 'draft', label: 'Draft', completionState: 'not_done', executionState: 'inactive' },
|
|
132
|
+
{ key: 'active_work', label: 'Active Work', completionState: 'not_done', executionState: 'active' },
|
|
133
|
+
{ key: 'finished', label: 'Finished', completionState: 'done', executionState: 'inactive' }
|
|
134
|
+
]
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const task = await engine.createTask({
|
|
138
|
+
projectId: proj.id,
|
|
139
|
+
title: 'Workflow Task',
|
|
140
|
+
status: 'active_work',
|
|
141
|
+
priority: 'medium',
|
|
142
|
+
plannedStartDate: '2026-08-01T00:00:00.000Z',
|
|
143
|
+
dueDate: '2026-08-10T00:00:00.000Z'
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const state = await engine.getTaskLifecycleState(task.id);
|
|
147
|
+
expect(state).not.toBeNull();
|
|
148
|
+
expect(state?.completionState).toBe('not_done');
|
|
149
|
+
expect(state?.executionState).toBe('active');
|
|
150
|
+
expect(state?.isActive).toBe(true);
|
|
151
|
+
expect(state?.isDone).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('builds task dependency graph with upstream and downstream tasks', async () => {
|
|
155
|
+
const engine = new CriticalPathEngine();
|
|
156
|
+
|
|
157
|
+
const proj = await engine.createProject({ key: 'DEP', name: 'Dependency Project' });
|
|
158
|
+
|
|
159
|
+
const taskA = await engine.createTask({ projectId: proj.id, title: 'Task A', status: 'todo', priority: 'medium' });
|
|
160
|
+
const taskB = await engine.createTask({ projectId: proj.id, title: 'Task B', status: 'todo', priority: 'medium' });
|
|
161
|
+
|
|
162
|
+
// Task B depends on Task A
|
|
163
|
+
await engine.store.addDependency({
|
|
164
|
+
taskId: taskB.id,
|
|
165
|
+
dependsOnTaskId: taskA.id,
|
|
166
|
+
type: 'blocking'
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const graphA = await engine.getTaskDependencyGraph(taskA.id);
|
|
170
|
+
expect(graphA.downstreamTasks.map((t) => t.id)).toContain(taskB.id);
|
|
171
|
+
|
|
172
|
+
const graphB = await engine.getTaskDependencyGraph(taskB.id);
|
|
173
|
+
expect(graphB.upstreamTasks.map((t) => t.id)).toContain(taskA.id);
|
|
174
|
+
});
|
|
63
175
|
});
|