@critical-path/core 0.32.0 → 0.33.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.
@@ -23,6 +23,9 @@ import { generateProjectKey } from '../utils/key.js';
23
23
  * SQLite returns NULL columns as `null`, while domain objects leave unset optional fields out.
24
24
  * Dropping nulls keeps SQLiteStore results identical to the other adapters.
25
25
  */
26
+ const EXTRA_TABLES = ['projects', 'tasks', 'teams', 'attachments'] as const;
27
+ type ExtraTable = (typeof EXTRA_TABLES)[number];
28
+
26
29
  function dropNulls<T extends object>(row: T): T {
27
30
  return Object.fromEntries(Object.entries(row).filter(([, value]) => value !== null)) as T;
28
31
  }
@@ -308,6 +311,16 @@ export class SQLiteStore implements StorageAdapter {
308
311
  }
309
312
  }
310
313
 
314
+ // Fields without a dedicated column are kept in a JSON `extra` column, so entities round-trip
315
+ // even when their types gain fields this schema does not know about.
316
+ for (const table of EXTRA_TABLES) {
317
+ try {
318
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN extra TEXT`);
319
+ } catch {
320
+ // Column may already exist
321
+ }
322
+ }
323
+
311
324
  for (const table of ['projects', 'workflows', 'teams']) {
312
325
  try {
313
326
  this.db.exec(`ALTER TABLE ${table} ADD COLUMN tenantId TEXT`);
@@ -317,6 +330,35 @@ export class SQLiteStore implements StorageAdapter {
317
330
  }
318
331
  }
319
332
 
333
+ private columnCache = new Map<string, Set<string>>();
334
+
335
+ private columnsOf(table: ExtraTable): Set<string> {
336
+ let columns = this.columnCache.get(table);
337
+ if (!columns) {
338
+ const info = this.db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
339
+ columns = new Set(info.map((c) => c.name));
340
+ this.columnCache.set(table, columns);
341
+ }
342
+ return columns;
343
+ }
344
+
345
+ /** Stores every defined field that has no dedicated column in the row's `extra` JSON. */
346
+ private writeExtra(table: ExtraTable, entity: object): void {
347
+ const columns = this.columnsOf(table);
348
+ const extra = Object.fromEntries(
349
+ Object.entries(entity).filter(([key, value]) => !columns.has(key) && value !== undefined)
350
+ );
351
+ this.db
352
+ .prepare(`UPDATE ${table} SET extra = ? WHERE id = ?`)
353
+ .run(Object.keys(extra).length ? JSON.stringify(extra) : null, (entity as { id: string }).id);
354
+ }
355
+
356
+ /** Merges the `extra` JSON back into a row. */
357
+ private expandExtra(row: any): any {
358
+ const { extra, ...rest } = row;
359
+ return extra ? { ...rest, ...JSON.parse(extra) } : rest;
360
+ }
361
+
320
362
  // --- Projects ---
321
363
  async getProjects(): Promise<Project[]> {
322
364
  const stmt = this.db.prepare('SELECT * FROM projects');
@@ -357,6 +399,7 @@ export class SQLiteStore implements StorageAdapter {
357
399
  newProj.createdAt,
358
400
  newProj.updatedAt
359
401
  );
402
+ this.writeExtra('projects', newProj);
360
403
  return newProj;
361
404
  }
362
405
 
@@ -390,6 +433,7 @@ export class SQLiteStore implements StorageAdapter {
390
433
  updated.updatedAt,
391
434
  id
392
435
  );
436
+ this.writeExtra('projects', updated);
393
437
  return updated;
394
438
  }
395
439
 
@@ -503,6 +547,7 @@ export class SQLiteStore implements StorageAdapter {
503
547
  newTeam.createdAt,
504
548
  newTeam.updatedAt
505
549
  );
550
+ this.writeExtra('teams', newTeam);
506
551
  return newTeam;
507
552
  }
508
553
 
@@ -523,6 +568,7 @@ export class SQLiteStore implements StorageAdapter {
523
568
  updated.updatedAt,
524
569
  id
525
570
  );
571
+ this.writeExtra('teams', updated);
526
572
  return updated;
527
573
  }
528
574
 
@@ -812,6 +858,7 @@ export class SQLiteStore implements StorageAdapter {
812
858
  newTask.createdAt,
813
859
  newTask.updatedAt
814
860
  );
861
+ this.writeExtra('tasks', newTask);
815
862
  return newTask;
816
863
  }
817
864
 
@@ -877,6 +924,7 @@ export class SQLiteStore implements StorageAdapter {
877
924
  updated.updatedAt,
878
925
  id
879
926
  );
927
+ this.writeExtra('tasks', updated);
880
928
  return updated;
881
929
  }
882
930
 
@@ -1099,7 +1147,7 @@ export class SQLiteStore implements StorageAdapter {
1099
1147
  const rows = stmt.all(...params) as any[];
1100
1148
  return rows.map((r) =>
1101
1149
  dropNulls({
1102
- ...r,
1150
+ ...this.expandExtra(r),
1103
1151
  sizeBytes: Number(r.sizeBytes),
1104
1152
  metadata: r.metadata ? JSON.parse(r.metadata) : undefined
1105
1153
  })
@@ -1111,7 +1159,7 @@ export class SQLiteStore implements StorageAdapter {
1111
1159
  const row = stmt.get(id) as any;
1112
1160
  if (!row) return null;
1113
1161
  return dropNulls({
1114
- ...row,
1162
+ ...this.expandExtra(row),
1115
1163
  sizeBytes: Number(row.sizeBytes),
1116
1164
  metadata: row.metadata ? JSON.parse(row.metadata) : undefined
1117
1165
  });
@@ -1147,6 +1195,7 @@ export class SQLiteStore implements StorageAdapter {
1147
1195
  newAtt.createdAt,
1148
1196
  newAtt.updatedAt
1149
1197
  );
1198
+ this.writeExtra('attachments', newAtt);
1150
1199
  return newAtt;
1151
1200
  }
1152
1201
 
@@ -1370,7 +1419,7 @@ export class SQLiteStore implements StorageAdapter {
1370
1419
  // Helper mappers
1371
1420
  private mapProject(row: any): Project {
1372
1421
  return dropNulls({
1373
- ...row,
1422
+ ...this.expandExtra(row),
1374
1423
  tenantId: row.tenantId ?? undefined,
1375
1424
  workflowId: row.workflowId || undefined,
1376
1425
  taskTypes: row.taskTypes ? JSON.parse(row.taskTypes) : undefined,
@@ -1395,7 +1444,7 @@ export class SQLiteStore implements StorageAdapter {
1395
1444
 
1396
1445
  private mapTeam(row: any): Team {
1397
1446
  return dropNulls({
1398
- ...row,
1447
+ ...this.expandExtra(row),
1399
1448
  tenantId: row.tenantId ?? undefined,
1400
1449
  memberIds: row.memberIds ? JSON.parse(row.memberIds) : []
1401
1450
  });
@@ -1403,7 +1452,7 @@ export class SQLiteStore implements StorageAdapter {
1403
1452
 
1404
1453
  private mapTask(row: any): Task {
1405
1454
  return dropNulls({
1406
- ...row,
1455
+ ...this.expandExtra(row),
1407
1456
  actualDurationSeconds: row.actualDurationSeconds ?? undefined,
1408
1457
  inProgressSince: row.inProgressSince || undefined,
1409
1458
  blockedDurationSeconds: row.blockedDurationSeconds ?? undefined,
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Storage adapter conformance suite. Run it against any `StorageAdapter` (including your own) to
3
+ * check it behaves like the built-in adapters:
4
+ *
5
+ * import { describe, it, expect } from 'vitest';
6
+ * import { runStorageAdapterConformance } from '@critical-path/core/testing';
7
+ * runStorageAdapterConformance({ name: 'PostgresStore', createStore: () => new PostgresStore(...), describe, it, expect });
8
+ *
9
+ * The test runner functions are passed in so core has no test-framework dependency.
10
+ */
11
+ import type { StorageAdapter } from '../store/index.js';
12
+ import type { Project, Task, Team, Workflow, Attachment, Deliverable, TaskContainer, Iteration, Comment } from '../types/index.js';
13
+
14
+ type Describe = (name: string, fn: () => void) => void;
15
+ type It = (name: string, fn: () => Promise<void> | void) => void;
16
+ // Minimal expect surface used by the suite (compatible with vitest and jest)
17
+ type Expect = (value: unknown) => {
18
+ toEqual(expected: unknown): void;
19
+ toBe(expected: unknown): void;
20
+ toBeNull(): void;
21
+ toBeUndefined(): void;
22
+ toBeDefined(): void;
23
+ toHaveLength(length: number): void;
24
+ toMatchObject(expected: object): void;
25
+ not: { toBeNull(): void; toHaveProperty(key: string): void };
26
+ };
27
+
28
+ export interface ConformanceOptions {
29
+ name: string;
30
+ createStore: () => StorageAdapter | Promise<StorageAdapter>;
31
+ describe: Describe;
32
+ it: It;
33
+ expect: Expect;
34
+ }
35
+
36
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
37
+
38
+ const schedule = {
39
+ name: 'Four-day week',
40
+ timezone: 'Europe/London',
41
+ defaultHoursPerDay: 8,
42
+ days: [{ dayOfWeek: 1, isWorkingDay: true, hours: [{ start: '09:00', end: '17:00' }] }],
43
+ holidays: [{ date: '2026-12-25', name: 'Christmas' }]
44
+ };
45
+
46
+ /** A project with every optional field set. */
47
+ const fullProject = (): Omit<Project, 'id' | 'createdAt' | 'updatedAt'> => ({
48
+ key: 'CONF',
49
+ name: 'Conformance',
50
+ description: 'Every field',
51
+ ownerId: 'owner-1',
52
+ members: [{ userId: 'u1', role: 'admin' }],
53
+ teamIds: ['team-1'],
54
+ workflowId: 'wf-1',
55
+ taskTypes: [{ key: 'bug', label: 'Bug' }],
56
+ statusDefinitions: [{ key: 'todo', label: 'To Do', category: 'not_started' }],
57
+ priorityDefinitions: [{ key: 'p1', label: 'P1', level: 1 }],
58
+ customFieldDefinitions: [{ id: 'f1', key: 'client', label: 'Client', type: 'text', required: false }],
59
+ schedule,
60
+ startDate: '2026-10-01',
61
+ targetEndDate: '2026-12-31',
62
+ tenantId: 'tenant-1'
63
+ });
64
+
65
+ /** A task with every optional field set. */
66
+ const fullTask = (projectId: string): Omit<Task, 'id' | 'createdAt' | 'updatedAt'> => ({
67
+ projectId,
68
+ key: 'CONF-1',
69
+ title: 'Every field',
70
+ description: 'All of them',
71
+ status: 'in_progress',
72
+ semanticStatus: 'in_progress',
73
+ priority: 'high',
74
+ taskType: 'bug',
75
+ assigneeId: 'u1',
76
+ assignees: [{ id: 'u1', name: 'Ana', role: 'lead', type: 'user', avatarUrl: 'https://example.com/a.png' }],
77
+ reporterId: 'u2',
78
+ reviewerId: 'u3',
79
+ iterationId: 'it-1',
80
+ teamId: 'team-1',
81
+ containerId: 'c-1',
82
+ deliverableId: 'd-1',
83
+ plannedStartDate: '2026-10-01',
84
+ actualStartDate: '2026-10-02T09:00:00.000Z',
85
+ dueDate: '2026-10-10',
86
+ estimatedHours: 8,
87
+ loggedHours: 2,
88
+ actualHours: 2,
89
+ billableHours: 1,
90
+ estimatedDurationMinutes: 480,
91
+ actualDurationMinutes: 120,
92
+ billableDurationMinutes: 60,
93
+ actualDurationSeconds: 7200,
94
+ inProgressSince: '2026-10-02T09:00:00.000Z',
95
+ blockedDurationSeconds: 60,
96
+ progress: 25,
97
+ isBlocked: false,
98
+ tags: ['a', 'b'],
99
+ todos: [{ id: 'todo-1', title: 'First', completed: false, createdAt: '2026-10-02T09:00:00.000Z' }],
100
+ customFields: { client: 'Acme', nested: { level: 2 } },
101
+ parentId: 'parent-1'
102
+ });
103
+
104
+ export function runStorageAdapterConformance({ name, createStore, describe, it, expect }: ConformanceOptions): void {
105
+ describe(`StorageAdapter conformance: ${name}`, () => {
106
+ it('round-trips every project field', async () => {
107
+ const store = await createStore();
108
+ const input = fullProject();
109
+ const created = await store.createProject(input);
110
+ expect(await store.getProject(created.id)).toMatchObject(input);
111
+ expect(await store.getProjects()).toHaveLength(1);
112
+ });
113
+
114
+ it('round-trips every task field', async () => {
115
+ const store = await createStore();
116
+ const project = await store.createProject({ name: 'P' });
117
+ const input = fullTask(project.id);
118
+ const created = await store.createTask(input);
119
+ expect(await store.getTask(created.id)).toMatchObject(input);
120
+ });
121
+
122
+ it('updates tasks partially, clears fields set to undefined, and replaces nested objects', async () => {
123
+ const store = await createStore();
124
+ const project = await store.createProject({ name: 'P' });
125
+ const created = await store.createTask(fullTask(project.id));
126
+
127
+ await store.updateTask(created.id, {
128
+ title: 'Renamed',
129
+ completedAt: undefined,
130
+ parentId: undefined,
131
+ iterationId: undefined,
132
+ customFields: { client: 'Globex' }
133
+ });
134
+ const updated = (await store.getTask(created.id)) as unknown as Record<string, unknown>;
135
+ expect(updated.title).toBe('Renamed');
136
+ expect(updated.description).toBe('All of them');
137
+ expect(updated.parentId).toBeUndefined();
138
+ expect(updated.iterationId).toBeUndefined();
139
+ expect(updated.customFields).toEqual({ client: 'Globex' });
140
+ expect(updated.parentId === null).toBe(false);
141
+ });
142
+
143
+ it('follows the not-found contract', async () => {
144
+ const store = await createStore();
145
+ expect(await store.getProject('missing')).toBeNull();
146
+ expect(await store.getTask('missing')).toBeNull();
147
+ expect(await store.updateTask('missing', { title: 'x' })).toBeNull();
148
+ expect(await store.updateProject('missing', { name: 'x' })).toBeNull();
149
+ expect(await store.deleteTask('missing')).toBe(false);
150
+ expect(await store.deleteProject('missing')).toBe(false);
151
+ expect(await store.getComment('missing')).toBeNull();
152
+ expect(await store.deleteComment('missing')).toBe(false);
153
+ expect(await store.getAttachment('missing')).toBeNull();
154
+ expect(await store.deleteAttachment('missing')).toBe(false);
155
+ expect(await store.getDependency('missing')).toBeNull();
156
+ expect(await store.removeDependency('missing')).toBe(false);
157
+ expect(await store.getWebhook('missing')).toBeNull();
158
+ expect(await store.deleteWebhook('missing')).toBe(false);
159
+ });
160
+
161
+ it('round-trips teams, workflows, containers, iterations and deliverables', async () => {
162
+ const store = await createStore();
163
+ const project = await store.createProject({ name: 'P' });
164
+
165
+ const team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'> = {
166
+ name: 'Core',
167
+ description: 'Team',
168
+ leaderId: 'u1',
169
+ memberIds: ['u1', 'u2'],
170
+ weeklyCapacityHours: 80,
171
+ schedule,
172
+ tenantId: 'tenant-1'
173
+ };
174
+ expect(await store.getTeam((await store.createTeam(team)).id)).toMatchObject(team);
175
+
176
+ const workflow: Omit<Workflow, 'id' | 'createdAt' | 'updatedAt'> = {
177
+ name: 'Flow',
178
+ description: 'Workflow',
179
+ statuses: [{ key: 'todo', label: 'To Do', category: 'not_started' }],
180
+ transitions: [{ id: 't1', name: 'Start', fromStatusKey: '*', toStatusKey: 'todo' }],
181
+ taskTypes: [{ key: 'bug', label: 'Bug' }],
182
+ defaultStatusKey: 'todo',
183
+ isDefault: true,
184
+ tenantId: 'tenant-1'
185
+ };
186
+ expect(await store.getWorkflow((await store.createWorkflow(workflow)).id)).toMatchObject(workflow);
187
+
188
+ const container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'> = {
189
+ projectId: project.id,
190
+ name: 'Epic',
191
+ description: 'Container',
192
+ parentId: 'c-0',
193
+ type: 'epic',
194
+ color: '#ff0000'
195
+ };
196
+ expect(await store.getContainer((await store.createContainer(container)).id)).toMatchObject(container);
197
+
198
+ const iteration: Omit<Iteration, 'id' | 'createdAt'> = {
199
+ projectId: project.id,
200
+ name: 'Sprint 1',
201
+ goal: 'Ship',
202
+ type: 'sprint',
203
+ startDate: '2026-10-01',
204
+ endDate: '2026-10-14',
205
+ status: 'active'
206
+ };
207
+ expect(await store.getIteration((await store.createIteration(iteration)).id)).toMatchObject(iteration);
208
+
209
+ const deliverable: Omit<Deliverable, 'id' | 'createdAt' | 'updatedAt'> = {
210
+ projectId: project.id,
211
+ title: 'Cut',
212
+ description: 'Deliverable',
213
+ status: 'planned',
214
+ format: '4K',
215
+ specs: { fps: 24 },
216
+ leadId: 'u1',
217
+ reviewerId: 'u2',
218
+ dueDate: '2026-11-01',
219
+ deliveredAt: '2026-11-02T00:00:00.000Z',
220
+ outputUrls: ['https://example.com/cut.mov'],
221
+ customFields: { client: 'Acme' }
222
+ };
223
+ expect(await store.getDeliverable((await store.createDeliverable(deliverable)).id)).toMatchObject(deliverable);
224
+ });
225
+
226
+ it('round-trips comments with reactions, ordered oldest first', async () => {
227
+ const store = await createStore();
228
+ const project = await store.createProject({ name: 'P' });
229
+ const task = await store.createTask({ projectId: project.id, title: 'T', status: 'todo', priority: 'medium' });
230
+ const input: Omit<Comment, 'id' | 'createdAt' | 'updatedAt'> = {
231
+ taskId: task.id,
232
+ authorId: 'u1',
233
+ authorType: 'agent',
234
+ content: 'First',
235
+ mentions: ['u2'],
236
+ metadata: { source: 'cli' }
237
+ };
238
+ const first = await store.addComment(input);
239
+ await sleep(2);
240
+ await store.addComment({ taskId: task.id, authorId: 'u2', content: 'Second' });
241
+
242
+ expect(await store.getComment(first.id)).toMatchObject(input);
243
+ expect((await store.getComments(task.id)).map((c) => c.content)).toEqual(['First', 'Second']);
244
+ });
245
+
246
+ it('round-trips attachments and applies every attachment filter together', async () => {
247
+ const store = await createStore();
248
+ const input: Omit<Attachment, 'id' | 'createdAt' | 'updatedAt'> = {
249
+ taskId: 't1',
250
+ projectId: 'p1',
251
+ commentId: 'c1',
252
+ uploaderId: 'u1',
253
+ uploaderType: 'user',
254
+ filename: 'spec.pdf',
255
+ mimeType: 'application/pdf',
256
+ sizeBytes: 1234,
257
+ url: 'https://example.com/spec.pdf',
258
+ storageKey: 'projects/p1/spec.pdf',
259
+ artifactType: 'spec',
260
+ metadata: { pages: 3 }
261
+ };
262
+ const created = await store.createAttachment(input);
263
+ await store.createAttachment({ ...input, commentId: 'c2' });
264
+ await store.createAttachment({ ...input, taskId: 't2' });
265
+
266
+ expect(await store.getAttachment(created.id)).toMatchObject(input);
267
+ expect(await store.getAttachments({ taskId: 't1', commentId: 'c1' })).toHaveLength(1);
268
+ expect(await store.getAttachments({ taskId: 't1' })).toHaveLength(2);
269
+ });
270
+
271
+ it('returns activities newest first and applies every activity filter together', async () => {
272
+ const store = await createStore();
273
+ await store.logActivity({ projectId: 'p1', taskId: 't1', actorId: 'u1', action: 'first', details: { n: 1 } });
274
+ await sleep(2);
275
+ await store.logActivity({ projectId: 'p1', taskId: 't2', actorId: 'u1', action: 'second', details: {} });
276
+ await sleep(2);
277
+ await store.logActivity({ projectId: 'p2', taskId: 't1', actorId: 'u1', action: 'third', details: {} });
278
+
279
+ expect((await store.getActivities({ projectId: 'p1' })).map((a) => a.action)).toEqual(['second', 'first']);
280
+ expect((await store.getActivities({ projectId: 'p1', taskId: 't1' })).map((a) => a.action)).toEqual(['first']);
281
+ expect((await store.getActivities()).map((a) => a.action)).toEqual(['third', 'second', 'first']);
282
+ expect((await store.getActivities({ taskId: 't1' }))[1]).toMatchObject({ details: { n: 1 } });
283
+ });
284
+
285
+ it('round-trips dependencies, time entries and webhooks', async () => {
286
+ const store = await createStore();
287
+ const dep = await store.addDependency({ taskId: 'a', dependsOnTaskId: 'b', type: 'blocked_by' });
288
+ expect(await store.getDependency(dep.id)).toEqual(dep);
289
+ expect(await store.getDependencies('b')).toHaveLength(1);
290
+ expect(await store.removeDependency(dep.id)).toBe(true);
291
+
292
+ const entry = await store.logTime({ taskId: 't1', userId: 'u1', hours: 1.5, isBillable: false, description: 'Work', loggedAt: '2026-10-01T10:00:00.000Z' });
293
+ expect((await store.getTimeEntries('t1'))[0]).toMatchObject({ id: entry.id, hours: 1.5, isBillable: false, description: 'Work', loggedAt: '2026-10-01T10:00:00.000Z' });
294
+ expect(await store.deleteTimeEntry(entry.id)).toBe(true);
295
+ expect(await store.getTimeEntries('t1')).toHaveLength(0);
296
+
297
+ const webhook = await store.addWebhook({ name: 'Hook', url: 'https://example.com/h', secret: 'whsec_x', events: ['*'], active: true, tenantId: 'tenant-1' });
298
+ expect(await store.getWebhook(webhook.id)).toMatchObject({ name: 'Hook', secret: 'whsec_x', events: ['*'], active: true, tenantId: 'tenant-1' });
299
+ });
300
+ });
301
+ }
@@ -0,0 +1 @@
1
+ export * from './conformance.js';
@@ -1 +1 @@
1
- {"root":["./src/deliverables.test.ts","./src/index.test.ts","./src/index.ts","./src/status.test.ts","./src/task-state.test.ts","./src/use-case-validation.test.ts","./src/domain/authorization.ts","./src/domain/calendar.test.ts","./src/domain/calendar.ts","./src/domain/cpm.test.ts","./src/domain/cpm.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/errors.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/domain/ladder.test.ts","./src/domain/ladder.ts","./src/domain/metrics.test.ts","./src/domain/metrics.ts","./src/domain/task-duration.test.ts","./src/domain/workload.test.ts","./src/domain/workload.ts","./src/engine/actor.test.ts","./src/engine/authorization.test.ts","./src/engine/cascade.test.ts","./src/engine/config.test.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/plugins/plugins.test.ts","./src/schemas/index.ts","./src/schemas/schemas.test.ts","./src/storage/file-storage.ts","./src/storage/firebase-storage.ts","./src/storage/index.ts","./src/storage/s3.ts","./src/storage/storage.test.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/query.test.ts","./src/store/query.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/types/index.ts","./src/utils/fractional-index.test.ts","./src/utils/fractional-index.ts","./src/utils/index.ts","./src/utils/key.test.ts","./src/utils/key.ts","./src/utils/mentions.test.ts","./src/utils/mentions.ts","./src/utils/status.ts","./src/utils/workflow.ts","./src/webhooks/dispatcher.ts","./src/webhooks/index.ts","./src/webhooks/signature.ts","./src/webhooks/webhooks.test.ts"],"version":"5.9.3"}
1
+ {"root":["./src/deliverables.test.ts","./src/index.test.ts","./src/index.ts","./src/status.test.ts","./src/task-state.test.ts","./src/use-case-validation.test.ts","./src/domain/authorization.ts","./src/domain/calendar.test.ts","./src/domain/calendar.ts","./src/domain/cpm.test.ts","./src/domain/cpm.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/errors.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/domain/ladder.test.ts","./src/domain/ladder.ts","./src/domain/metrics.test.ts","./src/domain/metrics.ts","./src/domain/task-duration.test.ts","./src/domain/workload.test.ts","./src/domain/workload.ts","./src/engine/actor.test.ts","./src/engine/authorization.test.ts","./src/engine/cascade.test.ts","./src/engine/config.test.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/plugins/plugins.test.ts","./src/schemas/index.ts","./src/schemas/schemas.test.ts","./src/storage/file-storage.ts","./src/storage/firebase-storage.ts","./src/storage/index.ts","./src/storage/s3.ts","./src/storage/storage.test.ts","./src/store/conformance.test.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/query.test.ts","./src/store/query.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/testing/conformance.ts","./src/testing/index.ts","./src/types/index.ts","./src/utils/fractional-index.test.ts","./src/utils/fractional-index.ts","./src/utils/index.ts","./src/utils/key.test.ts","./src/utils/key.ts","./src/utils/mentions.test.ts","./src/utils/mentions.ts","./src/utils/status.ts","./src/utils/workflow.ts","./src/webhooks/dispatcher.ts","./src/webhooks/index.ts","./src/webhooks/signature.ts","./src/webhooks/webhooks.test.ts"],"version":"5.9.3"}