@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.
Files changed (85) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +45 -0
  3. package/dist/domain/ddd.test.js +12 -0
  4. package/dist/domain/ddd.test.js.map +1 -1
  5. package/dist/domain/entities.d.ts.map +1 -1
  6. package/dist/domain/entities.js +3 -2
  7. package/dist/domain/entities.js.map +1 -1
  8. package/dist/domain/errors.d.ts +13 -0
  9. package/dist/domain/errors.d.ts.map +1 -0
  10. package/dist/domain/errors.js +19 -0
  11. package/dist/domain/errors.js.map +1 -0
  12. package/dist/domain/events.d.ts +9 -1
  13. package/dist/domain/events.d.ts.map +1 -1
  14. package/dist/domain/events.js.map +1 -1
  15. package/dist/domain/index.d.ts +1 -0
  16. package/dist/domain/index.d.ts.map +1 -1
  17. package/dist/domain/index.js +1 -0
  18. package/dist/domain/index.js.map +1 -1
  19. package/dist/domain/task-duration.test.js +32 -0
  20. package/dist/domain/task-duration.test.js.map +1 -1
  21. package/dist/engine/index.d.ts +11 -0
  22. package/dist/engine/index.d.ts.map +1 -1
  23. package/dist/engine/index.js +112 -19
  24. package/dist/engine/index.js.map +1 -1
  25. package/dist/index.test.js +20 -0
  26. package/dist/index.test.js.map +1 -1
  27. package/dist/status.test.js +97 -1
  28. package/dist/status.test.js.map +1 -1
  29. package/dist/store/sqlite.d.ts.map +1 -1
  30. package/dist/store/sqlite.js +19 -6
  31. package/dist/store/sqlite.js.map +1 -1
  32. package/dist/store/sqlite.test.js +20 -0
  33. package/dist/store/sqlite.test.js.map +1 -1
  34. package/dist/types/index.d.ts +2 -1
  35. package/dist/types/index.d.ts.map +1 -1
  36. package/dist/utils/fractional-index.d.ts +50 -0
  37. package/dist/utils/fractional-index.d.ts.map +1 -0
  38. package/dist/utils/fractional-index.js +226 -0
  39. package/dist/utils/fractional-index.js.map +1 -0
  40. package/dist/utils/fractional-index.test.d.ts +2 -0
  41. package/dist/utils/fractional-index.test.d.ts.map +1 -0
  42. package/dist/utils/fractional-index.test.js +182 -0
  43. package/dist/utils/fractional-index.test.js.map +1 -0
  44. package/dist/utils/index.d.ts +1 -0
  45. package/dist/utils/index.d.ts.map +1 -1
  46. package/dist/utils/index.js +1 -0
  47. package/dist/utils/index.js.map +1 -1
  48. package/dist/utils/key.d.ts +5 -0
  49. package/dist/utils/key.d.ts.map +1 -1
  50. package/dist/utils/key.js +10 -0
  51. package/dist/utils/key.js.map +1 -1
  52. package/dist/utils/key.test.js +18 -1
  53. package/dist/utils/key.test.js.map +1 -1
  54. package/dist/utils/mentions.d.ts +13 -5
  55. package/dist/utils/mentions.d.ts.map +1 -1
  56. package/dist/utils/mentions.js +52 -16
  57. package/dist/utils/mentions.js.map +1 -1
  58. package/dist/utils/mentions.test.js +32 -1
  59. package/dist/utils/mentions.test.js.map +1 -1
  60. package/dist/utils/status.d.ts +80 -1
  61. package/dist/utils/status.d.ts.map +1 -1
  62. package/dist/utils/status.js +194 -0
  63. package/dist/utils/status.js.map +1 -1
  64. package/package.json +1 -5
  65. package/src/domain/ddd.test.ts +22 -0
  66. package/src/domain/entities.ts +3 -2
  67. package/src/domain/errors.ts +19 -0
  68. package/src/domain/events.ts +6 -0
  69. package/src/domain/index.ts +1 -0
  70. package/src/domain/task-duration.test.ts +36 -0
  71. package/src/engine/index.ts +118 -20
  72. package/src/index.test.ts +24 -0
  73. package/src/status.test.ts +130 -2
  74. package/src/store/sqlite.test.ts +22 -0
  75. package/src/store/sqlite.ts +21 -4
  76. package/src/types/index.ts +2 -0
  77. package/src/utils/fractional-index.test.ts +228 -0
  78. package/src/utils/fractional-index.ts +254 -0
  79. package/src/utils/index.ts +2 -0
  80. package/src/utils/key.test.ts +22 -1
  81. package/src/utils/key.ts +11 -0
  82. package/src/utils/mentions.test.ts +37 -1
  83. package/src/utils/mentions.ts +58 -16
  84. package/src/utils/status.ts +247 -0
  85. package/tsconfig.tsbuildinfo +1 -1
@@ -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
@@ -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.20.2",
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",
@@ -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)', () => {
@@ -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 Error('Logged hours must be a positive number.');
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
+ }
@@ -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
@@ -1,6 +1,7 @@
1
1
  export * from './events.js';
2
2
  export * from './custom-fields.js';
3
3
  export * from './graph.js';
4
+ export * from './errors.js';
4
5
  export * from './entities.js';
5
6
  export * from './cpm.js';
6
7
  export * from './ladder.js';
@@ -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
+
@@ -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
- (existingStatusDef.category === 'completed' || existingStatusDef.category === 'canceled') &&
582
- processedUpdates.actualEndDate === undefined
626
+ existingStatusDef.category === 'completed' || existingStatusDef.category === 'canceled'
583
627
  ) {
584
- processedUpdates.actualEndDate = undefined;
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
- processedUpdates.actualEndDate = undefined;
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 ((newStatusDef.category === 'completed' || newStatusDef.category === 'canceled') && !processedUpdates.actualEndDate) {
596
- processedUpdates.actualEndDate = now;
597
- if (newStatusDef.category === 'completed' && processedUpdates.progress === undefined && (existing.progress || 0) < 100) {
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 cycleCheck = detectDependencyCycle(allProjectDeps, dep);
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 Error('Logged hours must be a positive number.');
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 Error(`Attachment with id "${id}" not found.`);
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 Error(`Project with ID "${projectId}" not found.`);
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