@critical-path/core 0.20.2 → 0.22.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/CHANGELOG.md +44 -0
- package/README.md +45 -0
- package/dist/domain/ddd.test.js +12 -0
- package/dist/domain/ddd.test.js.map +1 -1
- package/dist/domain/entities.d.ts.map +1 -1
- package/dist/domain/entities.js +3 -2
- package/dist/domain/entities.js.map +1 -1
- package/dist/domain/errors.d.ts +13 -0
- package/dist/domain/errors.d.ts.map +1 -0
- package/dist/domain/errors.js +19 -0
- package/dist/domain/errors.js.map +1 -0
- package/dist/domain/events.d.ts +9 -1
- package/dist/domain/events.d.ts.map +1 -1
- package/dist/domain/events.js.map +1 -1
- package/dist/domain/index.d.ts +1 -0
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +1 -0
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/task-duration.test.js +32 -0
- package/dist/domain/task-duration.test.js.map +1 -1
- package/dist/engine/index.d.ts +11 -0
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +112 -19
- package/dist/engine/index.js.map +1 -1
- package/dist/index.test.js +20 -0
- package/dist/index.test.js.map +1 -1
- package/dist/status.test.js +97 -1
- package/dist/status.test.js.map +1 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +19 -6
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/sqlite.test.js +20 -0
- package/dist/store/sqlite.test.js.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/fractional-index.d.ts +50 -0
- package/dist/utils/fractional-index.d.ts.map +1 -0
- package/dist/utils/fractional-index.js +226 -0
- package/dist/utils/fractional-index.js.map +1 -0
- package/dist/utils/fractional-index.test.d.ts +2 -0
- package/dist/utils/fractional-index.test.d.ts.map +1 -0
- package/dist/utils/fractional-index.test.js +182 -0
- package/dist/utils/fractional-index.test.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/key.d.ts +5 -0
- package/dist/utils/key.d.ts.map +1 -1
- package/dist/utils/key.js +10 -0
- package/dist/utils/key.js.map +1 -1
- package/dist/utils/key.test.js +18 -1
- package/dist/utils/key.test.js.map +1 -1
- package/dist/utils/mentions.d.ts +13 -5
- package/dist/utils/mentions.d.ts.map +1 -1
- package/dist/utils/mentions.js +52 -16
- package/dist/utils/mentions.js.map +1 -1
- package/dist/utils/mentions.test.js +32 -1
- package/dist/utils/mentions.test.js.map +1 -1
- package/dist/utils/status.d.ts +80 -1
- package/dist/utils/status.d.ts.map +1 -1
- package/dist/utils/status.js +194 -0
- package/dist/utils/status.js.map +1 -1
- package/package.json +1 -5
- package/src/domain/ddd.test.ts +22 -0
- package/src/domain/entities.ts +3 -2
- package/src/domain/errors.ts +19 -0
- package/src/domain/events.ts +6 -0
- package/src/domain/index.ts +1 -0
- package/src/domain/task-duration.test.ts +36 -0
- package/src/engine/index.ts +118 -20
- package/src/index.test.ts +24 -0
- package/src/status.test.ts +130 -2
- package/src/store/sqlite.test.ts +22 -0
- package/src/store/sqlite.ts +21 -4
- package/src/types/index.ts +2 -0
- package/src/utils/fractional-index.test.ts +228 -0
- package/src/utils/fractional-index.ts +254 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/key.test.ts +22 -1
- package/src/utils/key.ts +11 -0
- package/src/utils/mentions.test.ts +37 -1
- package/src/utils/mentions.ts +58 -16
- package/src/utils/status.ts +247 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/utils/status.js
CHANGED
|
@@ -139,4 +139,198 @@ export function deriveTaskLifecycleState(task, customDefinitionsOrOptions, legac
|
|
|
139
139
|
isCancelled
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Extracts a list of StatusDefinitions from either a Workflow or an array of definitions.
|
|
144
|
+
*/
|
|
145
|
+
export function extractStatusDefinitions(workflowOrStatuses) {
|
|
146
|
+
if (!workflowOrStatuses)
|
|
147
|
+
return undefined;
|
|
148
|
+
if (Array.isArray(workflowOrStatuses)) {
|
|
149
|
+
return workflowOrStatuses;
|
|
150
|
+
}
|
|
151
|
+
if (typeof workflowOrStatuses === 'object' && workflowOrStatuses !== null) {
|
|
152
|
+
const statuses = workflowOrStatuses.statuses;
|
|
153
|
+
if (Array.isArray(statuses)) {
|
|
154
|
+
return statuses;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Checks whether a task is flagged as a draft outside the active workflow.
|
|
161
|
+
*/
|
|
162
|
+
export function isDraftTask(task) {
|
|
163
|
+
if (!task)
|
|
164
|
+
return false;
|
|
165
|
+
return Boolean(task.isDraft);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Checks whether a task is archived.
|
|
169
|
+
*/
|
|
170
|
+
export function isArchivedTask(task) {
|
|
171
|
+
if (!task)
|
|
172
|
+
return false;
|
|
173
|
+
return (Boolean(task.archived) ||
|
|
174
|
+
Boolean(task.archivedAt) ||
|
|
175
|
+
task.status === 'archived' ||
|
|
176
|
+
Boolean(task.customFields?.isArchived));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Checks whether a task is soft-deleted / trashed.
|
|
180
|
+
*/
|
|
181
|
+
export function isTrashedTask(task) {
|
|
182
|
+
if (!task)
|
|
183
|
+
return false;
|
|
184
|
+
return (Boolean(task.trashed) ||
|
|
185
|
+
Boolean(task.trashedAt) ||
|
|
186
|
+
task.status === 'trashed' ||
|
|
187
|
+
Boolean(task.customFields?.isTrashed));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Checks whether a task is trashed, archived, or soft-deleted.
|
|
191
|
+
*/
|
|
192
|
+
export function isTrashedOrArchivedTask(task) {
|
|
193
|
+
if (!task)
|
|
194
|
+
return false;
|
|
195
|
+
return isTrashedTask(task) || isArchivedTask(task);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Determines whether a task is part of the legitimate project workflow.
|
|
199
|
+
* A workflow task is not trashed, not archived, and not a draft.
|
|
200
|
+
*/
|
|
201
|
+
export function isWorkflowTask(task) {
|
|
202
|
+
if (!task)
|
|
203
|
+
return false;
|
|
204
|
+
return !isTrashedTask(task) && !isArchivedTask(task) && !isDraftTask(task);
|
|
205
|
+
}
|
|
206
|
+
export const isTaskDraft = isDraftTask;
|
|
207
|
+
export const isTaskArchived = isArchivedTask;
|
|
208
|
+
export const isTaskTrashed = isTrashedTask;
|
|
209
|
+
export const isTaskTrashedOrArchived = isTrashedOrArchivedTask;
|
|
210
|
+
export const isTaskWorkflow = isWorkflowTask;
|
|
211
|
+
/**
|
|
212
|
+
* Resolves the canonical SemanticStatus for a task based on the project's workflow or default heuristics.
|
|
213
|
+
*/
|
|
214
|
+
export function getTaskSemanticStatus(task, workflowOrStatuses) {
|
|
215
|
+
if (!task)
|
|
216
|
+
return 'not_started';
|
|
217
|
+
const customDefs = extractStatusDefinitions(workflowOrStatuses);
|
|
218
|
+
// 1. Look up the task's status in custom workflow definitions
|
|
219
|
+
if (task.status) {
|
|
220
|
+
if (customDefs) {
|
|
221
|
+
const match = customDefs.find((s) => s.key === task.status);
|
|
222
|
+
if (match?.category)
|
|
223
|
+
return match.category;
|
|
224
|
+
if (match) {
|
|
225
|
+
if (match.completionState === 'done') {
|
|
226
|
+
return match.isCancelled ? 'canceled' : 'completed';
|
|
227
|
+
}
|
|
228
|
+
if (match.executionState === 'active') {
|
|
229
|
+
return 'in_progress';
|
|
230
|
+
}
|
|
231
|
+
if (match.completionState === 'not_done') {
|
|
232
|
+
return 'not_started';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// 2. Default status definition lookup from @critical-path/core for standard statuses
|
|
237
|
+
const normalizedKey = task.status.toLowerCase().replace(/[-\s]+/g, '_');
|
|
238
|
+
const defaultDef = DEFAULT_STATUS_DEFINITIONS[task.status] || DEFAULT_STATUS_DEFINITIONS[normalizedKey];
|
|
239
|
+
if (defaultDef?.category) {
|
|
240
|
+
return defaultDef.category;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// 3. If task has an explicit valid semanticStatus (for custom/unknown status), use it
|
|
244
|
+
if (task.semanticStatus) {
|
|
245
|
+
const raw = String(task.semanticStatus).toLowerCase();
|
|
246
|
+
if (raw === 'completed' ||
|
|
247
|
+
raw === 'in_progress' ||
|
|
248
|
+
raw === 'not_started' ||
|
|
249
|
+
raw === 'canceled' ||
|
|
250
|
+
raw === 'cancelled') {
|
|
251
|
+
return raw === 'cancelled' ? 'canceled' : raw;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// 4. Fallback to resolveStatusDefinition for general fallback or 'not_started'
|
|
255
|
+
if (task.status) {
|
|
256
|
+
const def = resolveStatusDefinition(task.status, customDefs);
|
|
257
|
+
if (def?.category) {
|
|
258
|
+
return def.category;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return 'not_started';
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Determines whether a task has reached completion in its lifecycle.
|
|
265
|
+
*/
|
|
266
|
+
export function isTaskCompleted(task, workflowOrStatuses) {
|
|
267
|
+
if (!task)
|
|
268
|
+
return false;
|
|
269
|
+
return getTaskSemanticStatus(task, workflowOrStatuses) === 'completed';
|
|
270
|
+
}
|
|
271
|
+
export const isTaskDone = isTaskCompleted;
|
|
272
|
+
/**
|
|
273
|
+
* Determines whether a task is actively in progress.
|
|
274
|
+
*/
|
|
275
|
+
export function isTaskInProgress(task, workflowOrStatuses) {
|
|
276
|
+
if (!task)
|
|
277
|
+
return false;
|
|
278
|
+
return getTaskSemanticStatus(task, workflowOrStatuses) === 'in_progress';
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Determines whether a task is pending or not yet started.
|
|
282
|
+
*/
|
|
283
|
+
export function isTaskNotStarted(task, workflowOrStatuses) {
|
|
284
|
+
if (!task)
|
|
285
|
+
return false;
|
|
286
|
+
return getTaskSemanticStatus(task, workflowOrStatuses) === 'not_started';
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Determines whether a task has been canceled or abandoned.
|
|
290
|
+
*/
|
|
291
|
+
export function isTaskCanceled(task, workflowOrStatuses) {
|
|
292
|
+
if (!task)
|
|
293
|
+
return false;
|
|
294
|
+
return getTaskSemanticStatus(task, workflowOrStatuses) === 'canceled';
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Determines whether a task is active in an ongoing project workflow.
|
|
298
|
+
* An active task:
|
|
299
|
+
* 1. Is not trashed or archived.
|
|
300
|
+
* 2. Is not a draft (exists within the active workflow).
|
|
301
|
+
* 3. Has not reached terminal completion ('completed') or terminal abandonment ('canceled').
|
|
302
|
+
*/
|
|
303
|
+
export function isTaskActive(task, workflowOrStatuses) {
|
|
304
|
+
if (!task)
|
|
305
|
+
return false;
|
|
306
|
+
if (isTrashedOrArchivedTask(task))
|
|
307
|
+
return false;
|
|
308
|
+
if (isDraftTask(task))
|
|
309
|
+
return false;
|
|
310
|
+
const sem = getTaskSemanticStatus(task, workflowOrStatuses);
|
|
311
|
+
return sem !== 'completed' && sem !== 'canceled';
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Checks whether a task currently has no assigned owner.
|
|
315
|
+
*/
|
|
316
|
+
export function isTaskUnassigned(task) {
|
|
317
|
+
if (!task)
|
|
318
|
+
return true;
|
|
319
|
+
const rawId = task.assigneeId ? String(task.assigneeId).trim() : '';
|
|
320
|
+
const hasAssigneeId = rawId !== '' && rawId.toLowerCase() !== 'unassigned';
|
|
321
|
+
const hasValidAssignees = Array.isArray(task.assignees) &&
|
|
322
|
+
task.assignees.some((a) => {
|
|
323
|
+
if (!a)
|
|
324
|
+
return false;
|
|
325
|
+
if (typeof a === 'string') {
|
|
326
|
+
const s = a.trim();
|
|
327
|
+
return s !== '' && s.toLowerCase() !== 'unassigned';
|
|
328
|
+
}
|
|
329
|
+
const id = a.id ? String(a.id).trim() : '';
|
|
330
|
+
const name = a.name ? String(a.name).trim() : '';
|
|
331
|
+
return ((id !== '' && id.toLowerCase() !== 'unassigned') ||
|
|
332
|
+
(name !== '' && name.toLowerCase() !== 'unassigned'));
|
|
333
|
+
});
|
|
334
|
+
return !hasAssigneeId && !hasValidAssignees;
|
|
335
|
+
}
|
|
142
336
|
//# sourceMappingURL=status.js.map
|
package/dist/utils/status.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/utils/status.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/utils/status.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@critical-path/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Core headless domain engine, plugin architecture, and data stores for Critical Path",
|
|
5
5
|
"author": "Jack James",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@types/better-sqlite3": "^9.6.0",
|
|
22
21
|
"@types/node": "^24.0.0",
|
|
23
22
|
"typescript": "^5.7.0",
|
|
24
23
|
"vitest": "^3.0.0"
|
|
@@ -26,9 +25,6 @@
|
|
|
26
25
|
"engines": {
|
|
27
26
|
"node": ">=24.0.0"
|
|
28
27
|
},
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"better-sqlite3": "^13.0.3"
|
|
31
|
-
},
|
|
32
28
|
"scripts": {
|
|
33
29
|
"build": "tsc --build",
|
|
34
30
|
"test": "vitest run",
|
package/src/domain/ddd.test.ts
CHANGED
|
@@ -250,6 +250,28 @@ describe('Domain-Driven Design (DDD) Enhancements Suite', () => {
|
|
|
250
250
|
})
|
|
251
251
|
).rejects.toThrow(CircularDependencyError);
|
|
252
252
|
});
|
|
253
|
+
|
|
254
|
+
it('detects cycles longer than the immediate neighbours in engine.addDependency', async () => {
|
|
255
|
+
const engine = new CriticalPathEngine();
|
|
256
|
+
const project = await engine.createProject({ name: 'Long Cycle' });
|
|
257
|
+
const [a, b, c, d] = await Promise.all(
|
|
258
|
+
['A', 'B', 'C', 'D'].map((title) => engine.createTask({ projectId: project.id, title }))
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
await engine.addDependency({ taskId: a.id, dependsOnTaskId: b.id, type: 'blocking' });
|
|
262
|
+
await engine.addDependency({ taskId: b.id, dependsOnTaskId: c.id, type: 'blocking' });
|
|
263
|
+
await engine.addDependency({ taskId: c.id, dependsOnTaskId: d.id, type: 'blocking' });
|
|
264
|
+
|
|
265
|
+
// D -> A closes A -> B -> C -> D -> A
|
|
266
|
+
await expect(
|
|
267
|
+
engine.addDependency({ taskId: d.id, dependsOnTaskId: a.id, type: 'blocking' })
|
|
268
|
+
).rejects.toThrow(CircularDependencyError);
|
|
269
|
+
|
|
270
|
+
// A diamond is not a cycle: A -> D is fine because D does not reach A
|
|
271
|
+
await expect(
|
|
272
|
+
engine.addDependency({ taskId: a.id, dependsOnTaskId: d.id, type: 'blocking' })
|
|
273
|
+
).resolves.toMatchObject({ taskId: a.id, dependsOnTaskId: d.id });
|
|
274
|
+
});
|
|
253
275
|
});
|
|
254
276
|
|
|
255
277
|
describe('Rich Domain Aggregates (TaskEntity & ProjectEntity)', () => {
|
package/src/domain/entities.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { validateTransition, WorkflowValidationError } from '../utils/workflow.j
|
|
|
29
29
|
import { resolveStatusDefinition } from '../utils/status.js';
|
|
30
30
|
import { validateCustomFieldValues } from './custom-fields.js';
|
|
31
31
|
import { generateProjectKey } from '../utils/key.js';
|
|
32
|
+
import { ValidationError } from './errors.js';
|
|
32
33
|
|
|
33
34
|
export abstract class BaseEntity {
|
|
34
35
|
public readonly id: string;
|
|
@@ -294,8 +295,8 @@ export class TaskEntity extends BaseEntity {
|
|
|
294
295
|
public logTime(
|
|
295
296
|
entry: { hours: number; isBillable?: boolean; userId?: string; description?: string }
|
|
296
297
|
): TimeEntry {
|
|
297
|
-
if (entry.hours <= 0) {
|
|
298
|
-
throw new
|
|
298
|
+
if (!Number.isFinite(entry.hours) || entry.hours <= 0) {
|
|
299
|
+
throw new ValidationError('Logged hours must be a positive number.');
|
|
299
300
|
}
|
|
300
301
|
|
|
301
302
|
this.loggedHours = (this.loggedHours || 0) + entry.hours;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when input fails a domain rule. Route handlers map it to HTTP 400.
|
|
3
|
+
*/
|
|
4
|
+
export class ValidationError extends Error {
|
|
5
|
+
constructor(message: string) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'ValidationError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Thrown when a referenced entity does not exist. Route handlers map it to HTTP 404.
|
|
13
|
+
*/
|
|
14
|
+
export class NotFoundError extends Error {
|
|
15
|
+
constructor(message: string) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'NotFoundError';
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/domain/events.ts
CHANGED
|
@@ -134,6 +134,11 @@ export interface ProjectUpdatedEvent extends DomainEvent<{ project: Project; pre
|
|
|
134
134
|
readonly aggregateType: 'Project';
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
export interface ProjectDeletedEvent extends DomainEvent<{ projectId: string; name: string; deletedTaskIds: string[] }> {
|
|
138
|
+
readonly name: 'project.deleted';
|
|
139
|
+
readonly aggregateType: 'Project';
|
|
140
|
+
}
|
|
141
|
+
|
|
137
142
|
export interface WorkflowCreatedEvent extends DomainEvent<{ workflow: Workflow }> {
|
|
138
143
|
readonly name: 'workflow.created';
|
|
139
144
|
readonly aggregateType: 'Workflow';
|
|
@@ -207,6 +212,7 @@ export type CriticalPathDomainEvent =
|
|
|
207
212
|
| TaskDependencyAddedEvent
|
|
208
213
|
| ProjectCreatedEvent
|
|
209
214
|
| ProjectUpdatedEvent
|
|
215
|
+
| ProjectDeletedEvent
|
|
210
216
|
| WorkflowCreatedEvent
|
|
211
217
|
| WorkflowUpdatedEvent
|
|
212
218
|
| WorkflowDeletedEvent
|
package/src/domain/index.ts
CHANGED
|
@@ -315,4 +315,40 @@ describe('Task Execution Timestamps & Cumulative In-Progress Duration', () => {
|
|
|
315
315
|
vi.useRealTimers();
|
|
316
316
|
}
|
|
317
317
|
});
|
|
318
|
+
|
|
319
|
+
it('sets completedAt and actualEndDate on completion and resets progress when reopening', async () => {
|
|
320
|
+
vi.useFakeTimers();
|
|
321
|
+
try {
|
|
322
|
+
const store = new InMemoryStore();
|
|
323
|
+
const engine = new CriticalPathEngine({ store });
|
|
324
|
+
|
|
325
|
+
const project = await engine.createProject({ name: 'Progress Transition Proj' });
|
|
326
|
+
vi.setSystemTime(new Date('2026-10-01T10:00:00Z'));
|
|
327
|
+
const task = await engine.createTask({
|
|
328
|
+
projectId: project.id,
|
|
329
|
+
title: 'Task for completion test',
|
|
330
|
+
status: 'in_progress',
|
|
331
|
+
progress: 50
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Complete the task
|
|
335
|
+
vi.setSystemTime(new Date('2026-10-01T12:00:00Z'));
|
|
336
|
+
const completed = await engine.updateTask(task.id, { status: 'done' });
|
|
337
|
+
expect(completed?.status).toBe('done');
|
|
338
|
+
expect(completed?.actualEndDate).toBe('2026-10-01T12:00:00.000Z');
|
|
339
|
+
expect(completed?.completedAt).toBe('2026-10-01T12:00:00.000Z');
|
|
340
|
+
expect(completed?.progress).toBe(100);
|
|
341
|
+
|
|
342
|
+
// Reopen to todo -> actualEndDate & completedAt cleared, progress reset from 100 to 0
|
|
343
|
+
vi.setSystemTime(new Date('2026-10-01T13:00:00Z'));
|
|
344
|
+
const reopened = await engine.updateTask(task.id, { status: 'todo' });
|
|
345
|
+
expect(reopened?.status).toBe('todo');
|
|
346
|
+
expect(reopened?.actualEndDate).toBeUndefined();
|
|
347
|
+
expect(reopened?.completedAt).toBeUndefined();
|
|
348
|
+
expect(reopened?.progress).toBe(0);
|
|
349
|
+
} finally {
|
|
350
|
+
vi.useRealTimers();
|
|
351
|
+
}
|
|
352
|
+
});
|
|
318
353
|
});
|
|
354
|
+
|
package/src/engine/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
TaskDeletedEvent,
|
|
57
57
|
ProjectCreatedEvent,
|
|
58
58
|
ProjectUpdatedEvent,
|
|
59
|
+
ProjectDeletedEvent,
|
|
59
60
|
WorkflowCreatedEvent,
|
|
60
61
|
WorkflowUpdatedEvent,
|
|
61
62
|
WorkflowDeletedEvent,
|
|
@@ -80,6 +81,7 @@ import {
|
|
|
80
81
|
import { validateAttachmentUrl, AttachmentValidationError } from '../domain/entities.js';
|
|
81
82
|
import { validateCustomFieldValues } from '../domain/custom-fields.js';
|
|
82
83
|
import { detectDependencyCycle, CircularDependencyError } from '../domain/graph.js';
|
|
84
|
+
import { ValidationError, NotFoundError } from '../domain/errors.js';
|
|
83
85
|
import { calculateCPM, type CPMOptions } from '../domain/cpm.js';
|
|
84
86
|
import { buildTimelineLadder, aggregateConcreteEvidenceForTask } from '../domain/ladder.js';
|
|
85
87
|
import {
|
|
@@ -340,6 +342,46 @@ export class CriticalPathEngine {
|
|
|
340
342
|
return updated;
|
|
341
343
|
}
|
|
342
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Deletes a project and its tasks. Each task goes through `deleteTask`, so plugin
|
|
347
|
+
* hooks run and `task.deleted` events fire before `project.deleted` is published.
|
|
348
|
+
*/
|
|
349
|
+
async deleteProject(id: string): Promise<boolean> {
|
|
350
|
+
const existing = await this.store.getProject(id);
|
|
351
|
+
if (!existing) return false;
|
|
352
|
+
|
|
353
|
+
const tasks = await this.store.getTasks(id);
|
|
354
|
+
const deletedTaskIds: string[] = [];
|
|
355
|
+
for (const task of tasks) {
|
|
356
|
+
if (await this.deleteTask(task.id)) {
|
|
357
|
+
deletedTaskIds.push(task.id);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const deleted = await this.store.deleteProject(id);
|
|
362
|
+
if (deleted) {
|
|
363
|
+
const now = new Date().toISOString();
|
|
364
|
+
const event: ProjectDeletedEvent = {
|
|
365
|
+
id: `evt_${Math.random().toString(36).substring(2, 9)}`,
|
|
366
|
+
name: 'project.deleted',
|
|
367
|
+
aggregateId: id,
|
|
368
|
+
aggregateType: 'Project',
|
|
369
|
+
occurredAt: now,
|
|
370
|
+
payload: { projectId: id, name: existing.name, deletedTaskIds }
|
|
371
|
+
};
|
|
372
|
+
await this.events.publish(event);
|
|
373
|
+
|
|
374
|
+
await this.store.logActivity({
|
|
375
|
+
projectId: id,
|
|
376
|
+
actorId: 'system',
|
|
377
|
+
action: 'project.deleted',
|
|
378
|
+
details: { name: existing.name, deletedTaskCount: deletedTaskIds.length }
|
|
379
|
+
});
|
|
380
|
+
this.dispatchWebhook('project.deleted', { projectId: id, name: existing.name, deletedTaskIds });
|
|
381
|
+
}
|
|
382
|
+
return deleted;
|
|
383
|
+
}
|
|
384
|
+
|
|
343
385
|
// --- Tasks ---
|
|
344
386
|
async getTasks(projectId?: string): Promise<Task[]> {
|
|
345
387
|
return this.store.getTasks(projectId);
|
|
@@ -373,6 +415,7 @@ export class CriticalPathEngine {
|
|
|
373
415
|
const actualStartDate = processedInput.actualStartDate ?? taskInput.actualStartDate ?? (statusDef.category === 'in_progress' ? now : undefined);
|
|
374
416
|
const inProgressSince = processedInput.inProgressSince ?? taskInput.inProgressSince ?? (statusDef.category === 'in_progress' ? now : undefined);
|
|
375
417
|
const actualEndDate = processedInput.actualEndDate ?? taskInput.actualEndDate ?? ((statusDef.category === 'completed' || statusDef.category === 'canceled') ? now : undefined);
|
|
418
|
+
const completedAt = processedInput.completedAt ?? taskInput.completedAt ?? (statusDef.category === 'completed' ? now : undefined);
|
|
376
419
|
|
|
377
420
|
const isInitialBlocked = processedInput.isBlocked ?? taskInput.isBlocked ?? false;
|
|
378
421
|
const blockedSince = processedInput.blockedSince ?? taskInput.blockedSince ?? (isInitialBlocked && statusDef.category === 'in_progress' ? now : undefined);
|
|
@@ -397,6 +440,7 @@ export class CriticalPathEngine {
|
|
|
397
440
|
plannedStartDate: processedInput.plannedStartDate ?? taskInput.plannedStartDate,
|
|
398
441
|
actualStartDate,
|
|
399
442
|
actualEndDate,
|
|
443
|
+
completedAt,
|
|
400
444
|
dueDate: processedInput.dueDate ?? taskInput.dueDate,
|
|
401
445
|
estimatedHours: processedInput.estimatedHours ?? taskInput.estimatedHours,
|
|
402
446
|
loggedHours: processedInput.loggedHours ?? taskInput.loggedHours ?? 0,
|
|
@@ -577,26 +621,54 @@ export class CriticalPathEngine {
|
|
|
577
621
|
if (isTaskBlocked && !processedUpdates.blockedSince) {
|
|
578
622
|
processedUpdates.blockedSince = now;
|
|
579
623
|
}
|
|
624
|
+
// Moving from completed/canceled to in_progress resets completion timestamp and progress
|
|
580
625
|
if (
|
|
581
|
-
|
|
582
|
-
processedUpdates.actualEndDate === undefined
|
|
626
|
+
existingStatusDef.category === 'completed' || existingStatusDef.category === 'canceled'
|
|
583
627
|
) {
|
|
584
|
-
processedUpdates.actualEndDate
|
|
628
|
+
if (processedUpdates.actualEndDate === undefined) {
|
|
629
|
+
processedUpdates.actualEndDate = undefined;
|
|
630
|
+
}
|
|
631
|
+
if (processedUpdates.completedAt === undefined) {
|
|
632
|
+
processedUpdates.completedAt = undefined;
|
|
633
|
+
}
|
|
634
|
+
if (existing.progress === 100 && processedUpdates.progress === undefined) {
|
|
635
|
+
processedUpdates.progress = 0;
|
|
636
|
+
}
|
|
585
637
|
}
|
|
586
638
|
} else if (
|
|
587
639
|
(existingStatusDef.category === 'completed' || existingStatusDef.category === 'canceled') &&
|
|
588
|
-
newStatusDef.category === 'not_started'
|
|
589
|
-
processedUpdates.actualEndDate === undefined
|
|
640
|
+
newStatusDef.category === 'not_started'
|
|
590
641
|
) {
|
|
591
|
-
|
|
642
|
+
// Moving from completed/canceled to not_started resets completion timestamp and progress
|
|
643
|
+
if (processedUpdates.actualEndDate === undefined) {
|
|
644
|
+
processedUpdates.actualEndDate = undefined;
|
|
645
|
+
}
|
|
646
|
+
if (processedUpdates.completedAt === undefined) {
|
|
647
|
+
processedUpdates.completedAt = undefined;
|
|
648
|
+
}
|
|
649
|
+
if (existing.progress === 100 && processedUpdates.progress === undefined) {
|
|
650
|
+
processedUpdates.progress = 0;
|
|
651
|
+
}
|
|
592
652
|
}
|
|
593
653
|
|
|
594
654
|
// 3. Entering completed or canceled
|
|
595
|
-
if (
|
|
596
|
-
processedUpdates.actualEndDate
|
|
597
|
-
|
|
655
|
+
if (newStatusDef.category === 'completed') {
|
|
656
|
+
if (!processedUpdates.actualEndDate) {
|
|
657
|
+
processedUpdates.actualEndDate = now;
|
|
658
|
+
}
|
|
659
|
+
if (!processedUpdates.completedAt) {
|
|
660
|
+
processedUpdates.completedAt = now;
|
|
661
|
+
}
|
|
662
|
+
if (processedUpdates.progress === undefined && (existing.progress || 0) < 100) {
|
|
598
663
|
processedUpdates.progress = 100;
|
|
599
664
|
}
|
|
665
|
+
} else if (newStatusDef.category === 'canceled') {
|
|
666
|
+
if (!processedUpdates.actualEndDate) {
|
|
667
|
+
processedUpdates.actualEndDate = now;
|
|
668
|
+
}
|
|
669
|
+
if (processedUpdates.completedAt === undefined) {
|
|
670
|
+
processedUpdates.completedAt = undefined;
|
|
671
|
+
}
|
|
600
672
|
}
|
|
601
673
|
}
|
|
602
674
|
|
|
@@ -886,14 +958,9 @@ export class CriticalPathEngine {
|
|
|
886
958
|
|
|
887
959
|
// --- Task Dependencies & Graph ---
|
|
888
960
|
async addDependency(dep: Omit<TaskDependency, 'id'>): Promise<TaskDependency> {
|
|
889
|
-
const existingDependencies = await this.store.getDependencies(dep.taskId);
|
|
890
|
-
const allProjectDeps = [
|
|
891
|
-
...existingDependencies,
|
|
892
|
-
...(await this.store.getDependencies(dep.dependsOnTaskId))
|
|
893
|
-
];
|
|
894
|
-
|
|
895
961
|
// Enforce Directed Acyclic Graph (DAG) Invariant
|
|
896
|
-
const
|
|
962
|
+
const reachableDeps = await this.collectUpstreamDependencies(dep.dependsOnTaskId);
|
|
963
|
+
const cycleCheck = detectDependencyCycle(reachableDeps, dep);
|
|
897
964
|
if (cycleCheck.hasCycle) {
|
|
898
965
|
throw new CircularDependencyError(dep.taskId, dep.dependsOnTaskId, cycleCheck.cyclePath);
|
|
899
966
|
}
|
|
@@ -914,6 +981,37 @@ export class CriticalPathEngine {
|
|
|
914
981
|
return created;
|
|
915
982
|
}
|
|
916
983
|
|
|
984
|
+
/**
|
|
985
|
+
* Collects every dependency edge reachable upstream from `startTaskId`. A new edge
|
|
986
|
+
* `taskId -> dependsOnTaskId` creates a cycle exactly when `taskId` is reachable from
|
|
987
|
+
* `dependsOnTaskId`, so this is the full set of edges the cycle check needs.
|
|
988
|
+
*/
|
|
989
|
+
private async collectUpstreamDependencies(startTaskId: string): Promise<TaskDependency[]> {
|
|
990
|
+
const edges = new Map<string, TaskDependency>();
|
|
991
|
+
const visited = new Set<string>();
|
|
992
|
+
let frontier = [startTaskId];
|
|
993
|
+
|
|
994
|
+
while (frontier.length > 0) {
|
|
995
|
+
const next: string[] = [];
|
|
996
|
+
const results = await Promise.all(frontier.map((id) => this.store.getDependencies(id)));
|
|
997
|
+
frontier.forEach((id) => visited.add(id));
|
|
998
|
+
|
|
999
|
+
for (const deps of results) {
|
|
1000
|
+
for (const d of deps) {
|
|
1001
|
+
// getDependencies returns edges in both directions; follow only outgoing ones.
|
|
1002
|
+
if (!visited.has(d.taskId)) continue;
|
|
1003
|
+
edges.set(d.id, d);
|
|
1004
|
+
if (!visited.has(d.dependsOnTaskId) && !next.includes(d.dependsOnTaskId)) {
|
|
1005
|
+
next.push(d.dependsOnTaskId);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
frontier = next;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
return Array.from(edges.values());
|
|
1013
|
+
}
|
|
1014
|
+
|
|
917
1015
|
async getTaskDependencyGraph(taskId: string): Promise<TaskDependencyGraph> {
|
|
918
1016
|
const dependencies = await this.store.getDependencies(taskId);
|
|
919
1017
|
const upstreamTaskIds = new Set<string>();
|
|
@@ -966,8 +1064,8 @@ export class CriticalPathEngine {
|
|
|
966
1064
|
|
|
967
1065
|
// --- Time Tracking ---
|
|
968
1066
|
async logTime(entry: Omit<TimeEntry, 'id' | 'loggedAt' | 'userId'> & { userId?: string; loggedAt?: string }): Promise<TimeEntry> {
|
|
969
|
-
if (entry.hours <= 0) {
|
|
970
|
-
throw new
|
|
1067
|
+
if (!Number.isFinite(entry.hours) || entry.hours <= 0) {
|
|
1068
|
+
throw new ValidationError('Logged hours must be a positive number.');
|
|
971
1069
|
}
|
|
972
1070
|
|
|
973
1071
|
const task = await this.store.getTask(entry.taskId);
|
|
@@ -1258,7 +1356,7 @@ export class CriticalPathEngine {
|
|
|
1258
1356
|
async readAttachmentText(id: string): Promise<string> {
|
|
1259
1357
|
const attachment = await this.getAttachment(id);
|
|
1260
1358
|
if (!attachment) {
|
|
1261
|
-
throw new
|
|
1359
|
+
throw new NotFoundError(`Attachment with id "${id}" not found.`);
|
|
1262
1360
|
}
|
|
1263
1361
|
|
|
1264
1362
|
if (attachment.storageKey && this.fileStorage && typeof this.fileStorage.download === 'function') {
|
|
@@ -1631,7 +1729,7 @@ export class CriticalPathEngine {
|
|
|
1631
1729
|
): Promise<TimelineLadder> {
|
|
1632
1730
|
const project = await this.store.getProject(projectId);
|
|
1633
1731
|
if (!project) {
|
|
1634
|
-
throw new
|
|
1732
|
+
throw new NotFoundError(`Project with ID "${projectId}" not found.`);
|
|
1635
1733
|
}
|
|
1636
1734
|
|
|
1637
1735
|
let tasks = await this.store.getTasks(projectId);
|
package/src/index.test.ts
CHANGED
|
@@ -3,6 +3,30 @@ import { CriticalPathEngine, deriveTaskLifecycleState, DEFAULT_VFX_WORKFLOW } fr
|
|
|
3
3
|
import type { CriticalPathPlugin } from './types/index.js';
|
|
4
4
|
|
|
5
5
|
describe('CriticalPathEngine Core Tests', () => {
|
|
6
|
+
it('deletes a project with its tasks and publishes deletion events', async () => {
|
|
7
|
+
const engine = new CriticalPathEngine();
|
|
8
|
+
const proj = await engine.createProject({ key: 'DEL', name: 'Doomed Project' });
|
|
9
|
+
const keep = await engine.createProject({ key: 'KEEP', name: 'Survivor' });
|
|
10
|
+
const t1 = await engine.createTask({ projectId: proj.id, title: 'One' });
|
|
11
|
+
const t2 = await engine.createTask({ projectId: proj.id, title: 'Two' });
|
|
12
|
+
const other = await engine.createTask({ projectId: keep.id, title: 'Other' });
|
|
13
|
+
|
|
14
|
+
const published: string[] = [];
|
|
15
|
+
engine.events.subscribe('*', (event) => {
|
|
16
|
+
published.push(event.name);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(await engine.deleteProject(proj.id)).toBe(true);
|
|
20
|
+
expect(await engine.getProject(proj.id)).toBeNull();
|
|
21
|
+
expect(await engine.getTask(t1.id)).toBeNull();
|
|
22
|
+
expect(await engine.getTask(t2.id)).toBeNull();
|
|
23
|
+
expect(await engine.getTask(other.id)).not.toBeNull();
|
|
24
|
+
expect(published.filter((n) => n === 'task.deleted')).toHaveLength(2);
|
|
25
|
+
expect(published.at(-1)).toBe('project.deleted');
|
|
26
|
+
|
|
27
|
+
expect(await engine.deleteProject(proj.id)).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
6
30
|
it('creates projects and tasks with activity logs', async () => {
|
|
7
31
|
const engine = new CriticalPathEngine();
|
|
8
32
|
|