@critical-path/core 0.2.0 → 0.3.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 (57) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/engine/index.d.ts +19 -1
  3. package/dist/engine/index.d.ts.map +1 -1
  4. package/dist/engine/index.js +129 -6
  5. package/dist/engine/index.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.test.js +96 -1
  11. package/dist/index.test.js.map +1 -1
  12. package/dist/store/firebase.d.ts +19 -7
  13. package/dist/store/firebase.d.ts.map +1 -1
  14. package/dist/store/firebase.js +98 -14
  15. package/dist/store/firebase.js.map +1 -1
  16. package/dist/store/firebase.test.js +5 -2
  17. package/dist/store/firebase.test.js.map +1 -1
  18. package/dist/store/index.d.ts +34 -8
  19. package/dist/store/index.d.ts.map +1 -1
  20. package/dist/store/index.js +79 -12
  21. package/dist/store/index.js.map +1 -1
  22. package/dist/store/sqlite.d.ts +17 -4
  23. package/dist/store/sqlite.d.ts.map +1 -1
  24. package/dist/store/sqlite.js +179 -39
  25. package/dist/store/sqlite.js.map +1 -1
  26. package/dist/types/index.d.ts +70 -9
  27. package/dist/types/index.d.ts.map +1 -1
  28. package/dist/utils/index.d.ts +3 -0
  29. package/dist/utils/index.d.ts.map +1 -0
  30. package/dist/utils/index.js +3 -0
  31. package/dist/utils/index.js.map +1 -0
  32. package/dist/utils/key.d.ts +14 -0
  33. package/dist/utils/key.d.ts.map +1 -0
  34. package/dist/utils/key.js +49 -0
  35. package/dist/utils/key.js.map +1 -0
  36. package/dist/utils/key.test.d.ts +2 -0
  37. package/dist/utils/key.test.d.ts.map +1 -0
  38. package/dist/utils/key.test.js +48 -0
  39. package/dist/utils/key.test.js.map +1 -0
  40. package/dist/utils/status.d.ts +14 -0
  41. package/dist/utils/status.d.ts.map +1 -0
  42. package/dist/utils/status.js +58 -0
  43. package/dist/utils/status.js.map +1 -0
  44. package/package.json +5 -1
  45. package/src/engine/index.ts +161 -8
  46. package/src/index.test.ts +113 -1
  47. package/src/index.ts +2 -0
  48. package/src/store/firebase.test.ts +8 -2
  49. package/src/store/firebase.ts +112 -18
  50. package/src/store/index.ts +113 -18
  51. package/src/store/sqlite.ts +277 -47
  52. package/src/types/index.ts +76 -10
  53. package/src/utils/index.ts +2 -0
  54. package/src/utils/key.test.ts +55 -0
  55. package/src/utils/key.ts +51 -0
  56. package/src/utils/status.ts +85 -0
  57. package/tsconfig.tsbuildinfo +1 -1
@@ -3,13 +3,16 @@ import type { StorageAdapter } from './index.js';
3
3
  import type {
4
4
  Project,
5
5
  Task,
6
- Sprint,
6
+ Iteration,
7
+ Team,
8
+ TaskContainer,
7
9
  Comment,
8
10
  TimeEntry,
9
11
  Activity,
10
12
  Webhook,
11
13
  TaskDependency
12
14
  } from '../types/index.js';
15
+ import { generateProjectKey } from '../utils/key.js';
13
16
 
14
17
  export interface SQLiteStoreConfig {
15
18
  /**
@@ -44,11 +47,36 @@ export class SQLiteStore implements StorageAdapter {
44
47
  description TEXT,
45
48
  ownerId TEXT,
46
49
  members TEXT,
50
+ teamIds TEXT,
51
+ statusDefinitions TEXT,
52
+ priorityDefinitions TEXT,
47
53
  customFieldDefinitions TEXT,
48
54
  createdAt TEXT NOT NULL,
49
55
  updatedAt TEXT NOT NULL
50
56
  );
51
57
 
58
+ CREATE TABLE IF NOT EXISTS teams (
59
+ id TEXT PRIMARY KEY,
60
+ name TEXT NOT NULL,
61
+ description TEXT,
62
+ leaderId TEXT,
63
+ memberIds TEXT NOT NULL,
64
+ createdAt TEXT NOT NULL,
65
+ updatedAt TEXT NOT NULL
66
+ );
67
+
68
+ CREATE TABLE IF NOT EXISTS containers (
69
+ id TEXT PRIMARY KEY,
70
+ projectId TEXT NOT NULL,
71
+ name TEXT NOT NULL,
72
+ description TEXT,
73
+ parentId TEXT,
74
+ type TEXT,
75
+ color TEXT,
76
+ createdAt TEXT NOT NULL,
77
+ updatedAt TEXT NOT NULL
78
+ );
79
+
52
80
  CREATE TABLE IF NOT EXISTS tasks (
53
81
  id TEXT PRIMARY KEY,
54
82
  projectId TEXT NOT NULL,
@@ -58,10 +86,22 @@ export class SQLiteStore implements StorageAdapter {
58
86
  priority TEXT NOT NULL,
59
87
  assigneeId TEXT,
60
88
  reporterId TEXT,
61
- sprintId TEXT,
89
+ reviewerId TEXT,
90
+ iterationId TEXT,
91
+ teamId TEXT,
92
+ containerId TEXT,
93
+ plannedStartDate TEXT,
94
+ actualStartDate TEXT,
95
+ actualEndDate TEXT,
62
96
  dueDate TEXT,
63
97
  estimatedHours REAL,
64
98
  loggedHours REAL,
99
+ actualHours REAL,
100
+ billableHours REAL,
101
+ estimatedDurationMinutes REAL,
102
+ actualDurationMinutes REAL,
103
+ billableDurationMinutes REAL,
104
+ progress REAL,
65
105
  tags TEXT,
66
106
  customFields TEXT,
67
107
  parentId TEXT,
@@ -69,11 +109,12 @@ export class SQLiteStore implements StorageAdapter {
69
109
  updatedAt TEXT NOT NULL
70
110
  );
71
111
 
72
- CREATE TABLE IF NOT EXISTS sprints (
112
+ CREATE TABLE IF NOT EXISTS iterations (
73
113
  id TEXT PRIMARY KEY,
74
114
  projectId TEXT NOT NULL,
75
115
  name TEXT NOT NULL,
76
116
  goal TEXT,
117
+ type TEXT,
77
118
  startDate TEXT,
78
119
  endDate TEXT,
79
120
  status TEXT NOT NULL,
@@ -104,6 +145,7 @@ export class SQLiteStore implements StorageAdapter {
104
145
  taskId TEXT NOT NULL,
105
146
  userId TEXT NOT NULL,
106
147
  hours REAL NOT NULL,
148
+ isBillable INTEGER,
107
149
  description TEXT,
108
150
  loggedAt TEXT NOT NULL
109
151
  );
@@ -142,19 +184,23 @@ export class SQLiteStore implements StorageAdapter {
142
184
  async createProject(project: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>): Promise<Project> {
143
185
  const id = `proj_${Math.random().toString(36).substring(2, 9)}`;
144
186
  const now = new Date().toISOString();
145
- const newProj: Project = { ...project, id, createdAt: now, updatedAt: now };
187
+ const key = project.key || generateProjectKey(project.name);
188
+ const newProj: Project = { ...project, key, id, createdAt: now, updatedAt: now };
146
189
 
147
190
  const stmt = this.db.prepare(`
148
- INSERT INTO projects (id, key, name, description, ownerId, members, customFieldDefinitions, createdAt, updatedAt)
149
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
191
+ INSERT INTO projects (id, key, name, description, ownerId, members, teamIds, statusDefinitions, priorityDefinitions, customFieldDefinitions, createdAt, updatedAt)
192
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
150
193
  `);
151
194
  stmt.run(
152
195
  newProj.id,
153
- newProj.key,
196
+ newProj.key || '',
154
197
  newProj.name,
155
198
  newProj.description || null,
156
199
  newProj.ownerId || null,
157
200
  JSON.stringify(newProj.members || []),
201
+ JSON.stringify(newProj.teamIds || []),
202
+ JSON.stringify(newProj.statusDefinitions || []),
203
+ JSON.stringify(newProj.priorityDefinitions || []),
158
204
  JSON.stringify(newProj.customFieldDefinitions || []),
159
205
  newProj.createdAt,
160
206
  newProj.updatedAt
@@ -174,15 +220,18 @@ export class SQLiteStore implements StorageAdapter {
174
220
 
175
221
  const stmt = this.db.prepare(`
176
222
  UPDATE projects
177
- SET key = ?, name = ?, description = ?, ownerId = ?, members = ?, customFieldDefinitions = ?, updatedAt = ?
223
+ SET key = ?, name = ?, description = ?, ownerId = ?, members = ?, teamIds = ?, statusDefinitions = ?, priorityDefinitions = ?, customFieldDefinitions = ?, updatedAt = ?
178
224
  WHERE id = ?
179
225
  `);
180
226
  stmt.run(
181
- updated.key,
227
+ updated.key || '',
182
228
  updated.name,
183
229
  updated.description || null,
184
230
  updated.ownerId || null,
185
231
  JSON.stringify(updated.members || []),
232
+ JSON.stringify(updated.teamIds || []),
233
+ JSON.stringify(updated.statusDefinitions || []),
234
+ JSON.stringify(updated.priorityDefinitions || []),
186
235
  JSON.stringify(updated.customFieldDefinitions || []),
187
236
  updated.updatedAt,
188
237
  id
@@ -196,6 +245,129 @@ export class SQLiteStore implements StorageAdapter {
196
245
  return (result.changes ?? 0) > 0;
197
246
  }
198
247
 
248
+ // --- Teams ---
249
+ async getTeams(): Promise<Team[]> {
250
+ const stmt = this.db.prepare('SELECT * FROM teams');
251
+ const rows = stmt.all() as any[];
252
+ return rows.map((r) => this.mapTeam(r));
253
+ }
254
+
255
+ async getTeam(id: string): Promise<Team | null> {
256
+ const stmt = this.db.prepare('SELECT * FROM teams WHERE id = ?');
257
+ const row = stmt.get(id) as any;
258
+ return row ? this.mapTeam(row) : null;
259
+ }
260
+
261
+ async createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team> {
262
+ const id = `team_${Math.random().toString(36).substring(2, 9)}`;
263
+ const now = new Date().toISOString();
264
+ const newTeam: Team = { ...team, id, createdAt: now, updatedAt: now };
265
+
266
+ const stmt = this.db.prepare(`
267
+ INSERT INTO teams (id, name, description, leaderId, memberIds, createdAt, updatedAt)
268
+ VALUES (?, ?, ?, ?, ?, ?, ?)
269
+ `);
270
+ stmt.run(
271
+ newTeam.id,
272
+ newTeam.name,
273
+ newTeam.description || null,
274
+ newTeam.leaderId || null,
275
+ JSON.stringify(newTeam.memberIds || []),
276
+ newTeam.createdAt,
277
+ newTeam.updatedAt
278
+ );
279
+ return newTeam;
280
+ }
281
+
282
+ async updateTeam(id: string, updates: Partial<Team>): Promise<Team | null> {
283
+ const existing = await this.getTeam(id);
284
+ if (!existing) return null;
285
+
286
+ const updated: Team = { ...existing, ...updates, updatedAt: new Date().toISOString() };
287
+ const stmt = this.db.prepare(`
288
+ UPDATE teams SET name = ?, description = ?, leaderId = ?, memberIds = ?, updatedAt = ?
289
+ WHERE id = ?
290
+ `);
291
+ stmt.run(
292
+ updated.name,
293
+ updated.description || null,
294
+ updated.leaderId || null,
295
+ JSON.stringify(updated.memberIds || []),
296
+ updated.updatedAt,
297
+ id
298
+ );
299
+ return updated;
300
+ }
301
+
302
+ async deleteTeam(id: string): Promise<boolean> {
303
+ const stmt = this.db.prepare('DELETE FROM teams WHERE id = ?');
304
+ const result = stmt.run(id);
305
+ return (result.changes ?? 0) > 0;
306
+ }
307
+
308
+ // --- Task Containers ---
309
+ async getContainers(projectId: string): Promise<TaskContainer[]> {
310
+ const stmt = this.db.prepare('SELECT * FROM containers WHERE projectId = ?');
311
+ const rows = stmt.all(projectId) as any[];
312
+ return rows;
313
+ }
314
+
315
+ async getContainer(id: string): Promise<TaskContainer | null> {
316
+ const stmt = this.db.prepare('SELECT * FROM containers WHERE id = ?');
317
+ const row = stmt.get(id) as any;
318
+ return row || null;
319
+ }
320
+
321
+ async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
322
+ const id = `cnt_${Math.random().toString(36).substring(2, 9)}`;
323
+ const now = new Date().toISOString();
324
+ const newContainer: TaskContainer = { ...container, id, createdAt: now, updatedAt: now };
325
+
326
+ const stmt = this.db.prepare(`
327
+ INSERT INTO containers (id, projectId, name, description, parentId, type, color, createdAt, updatedAt)
328
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
329
+ `);
330
+ stmt.run(
331
+ newContainer.id,
332
+ newContainer.projectId,
333
+ newContainer.name,
334
+ newContainer.description || null,
335
+ newContainer.parentId || null,
336
+ newContainer.type || null,
337
+ newContainer.color || null,
338
+ newContainer.createdAt,
339
+ newContainer.updatedAt
340
+ );
341
+ return newContainer;
342
+ }
343
+
344
+ async updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null> {
345
+ const existing = await this.getContainer(id);
346
+ if (!existing) return null;
347
+
348
+ const updated: TaskContainer = { ...existing, ...updates, updatedAt: new Date().toISOString() };
349
+ const stmt = this.db.prepare(`
350
+ UPDATE containers SET name = ?, description = ?, parentId = ?, type = ?, color = ?, updatedAt = ?
351
+ WHERE id = ?
352
+ `);
353
+ stmt.run(
354
+ updated.name,
355
+ updated.description || null,
356
+ updated.parentId || null,
357
+ updated.type || null,
358
+ updated.color || null,
359
+ updated.updatedAt,
360
+ id
361
+ );
362
+ return updated;
363
+ }
364
+
365
+ async deleteContainer(id: string): Promise<boolean> {
366
+ const stmt = this.db.prepare('DELETE FROM containers WHERE id = ?');
367
+ const result = stmt.run(id);
368
+ return (result.changes ?? 0) > 0;
369
+ }
370
+
199
371
  // --- Tasks ---
200
372
  async getTasks(projectId?: string): Promise<Task[]> {
201
373
  let rows: any[];
@@ -223,8 +395,11 @@ export class SQLiteStore implements StorageAdapter {
223
395
  const stmt = this.db.prepare(`
224
396
  INSERT INTO tasks (
225
397
  id, projectId, title, description, status, priority, assigneeId, reporterId,
226
- sprintId, dueDate, estimatedHours, loggedHours, tags, customFields, parentId, createdAt, updatedAt
227
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
398
+ reviewerId, iterationId, teamId, containerId, plannedStartDate, actualStartDate, actualEndDate,
399
+ dueDate, estimatedHours, loggedHours, actualHours, billableHours,
400
+ estimatedDurationMinutes, actualDurationMinutes, billableDurationMinutes, progress,
401
+ tags, customFields, parentId, createdAt, updatedAt
402
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
228
403
  `);
229
404
  stmt.run(
230
405
  newTask.id,
@@ -235,10 +410,22 @@ export class SQLiteStore implements StorageAdapter {
235
410
  newTask.priority,
236
411
  newTask.assigneeId || null,
237
412
  newTask.reporterId || null,
238
- newTask.sprintId || null,
413
+ newTask.reviewerId || null,
414
+ newTask.iterationId || null,
415
+ newTask.teamId || null,
416
+ newTask.containerId || null,
417
+ newTask.plannedStartDate || null,
418
+ newTask.actualStartDate || null,
419
+ newTask.actualEndDate || null,
239
420
  newTask.dueDate || null,
240
421
  newTask.estimatedHours ?? null,
241
422
  newTask.loggedHours ?? null,
423
+ newTask.actualHours ?? null,
424
+ newTask.billableHours ?? null,
425
+ newTask.estimatedDurationMinutes ?? null,
426
+ newTask.actualDurationMinutes ?? null,
427
+ newTask.billableDurationMinutes ?? null,
428
+ newTask.progress ?? null,
242
429
  JSON.stringify(newTask.tags || []),
243
430
  JSON.stringify(newTask.customFields || {}),
244
431
  newTask.parentId || null,
@@ -261,8 +448,11 @@ export class SQLiteStore implements StorageAdapter {
261
448
  const stmt = this.db.prepare(`
262
449
  UPDATE tasks SET
263
450
  projectId = ?, title = ?, description = ?, status = ?, priority = ?,
264
- assigneeId = ?, reporterId = ?, sprintId = ?, dueDate = ?,
265
- estimatedHours = ?, loggedHours = ?, tags = ?, customFields = ?, parentId = ?, updatedAt = ?
451
+ assigneeId = ?, reporterId = ?, reviewerId = ?, iterationId = ?, teamId = ?, containerId = ?,
452
+ plannedStartDate = ?, actualStartDate = ?, actualEndDate = ?, dueDate = ?,
453
+ estimatedHours = ?, loggedHours = ?, actualHours = ?, billableHours = ?,
454
+ estimatedDurationMinutes = ?, actualDurationMinutes = ?, billableDurationMinutes = ?, progress = ?,
455
+ tags = ?, customFields = ?, parentId = ?, updatedAt = ?
266
456
  WHERE id = ?
267
457
  `);
268
458
  stmt.run(
@@ -273,10 +463,22 @@ export class SQLiteStore implements StorageAdapter {
273
463
  updated.priority,
274
464
  updated.assigneeId || null,
275
465
  updated.reporterId || null,
276
- updated.sprintId || null,
466
+ updated.reviewerId || null,
467
+ updated.iterationId || null,
468
+ updated.teamId || null,
469
+ updated.containerId || null,
470
+ updated.plannedStartDate || null,
471
+ updated.actualStartDate || null,
472
+ updated.actualEndDate || null,
277
473
  updated.dueDate || null,
278
474
  updated.estimatedHours ?? null,
279
475
  updated.loggedHours ?? null,
476
+ updated.actualHours ?? null,
477
+ updated.billableHours ?? null,
478
+ updated.estimatedDurationMinutes ?? null,
479
+ updated.actualDurationMinutes ?? null,
480
+ updated.billableDurationMinutes ?? null,
481
+ updated.progress ?? null,
280
482
  JSON.stringify(updated.tags || []),
281
483
  JSON.stringify(updated.customFields || {}),
282
484
  updated.parentId || null,
@@ -292,53 +494,56 @@ export class SQLiteStore implements StorageAdapter {
292
494
  return (result.changes ?? 0) > 0;
293
495
  }
294
496
 
295
- // --- Sprints ---
296
- async getSprints(projectId: string): Promise<Sprint[]> {
297
- const stmt = this.db.prepare('SELECT * FROM sprints WHERE projectId = ?');
497
+ // --- Iterations ---
498
+ async getIterations(projectId: string): Promise<Iteration[]> {
499
+ const stmt = this.db.prepare('SELECT * FROM iterations WHERE projectId = ?');
298
500
  const rows = stmt.all(projectId) as any[];
299
- return rows.map((r) => ({
300
- ...r,
301
- description: r.goal
302
- }));
501
+ return rows;
303
502
  }
304
503
 
305
- async createSprint(sprint: Omit<Sprint, 'id' | 'createdAt'>): Promise<Sprint> {
306
- const id = `sprint_${Math.random().toString(36).substring(2, 9)}`;
504
+ async getIteration(id: string): Promise<Iteration | null> {
505
+ const stmt = this.db.prepare('SELECT * FROM iterations WHERE id = ?');
506
+ const row = stmt.get(id) as any;
507
+ return row || null;
508
+ }
509
+
510
+ async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
511
+ const id = `iter_${Math.random().toString(36).substring(2, 9)}`;
307
512
  const now = new Date().toISOString();
308
- const newSprint: Sprint = { ...sprint, id, createdAt: now };
513
+ const newIteration: Iteration = { ...iteration, id, createdAt: now };
309
514
 
310
515
  const stmt = this.db.prepare(`
311
- INSERT INTO sprints (id, projectId, name, goal, startDate, endDate, status, createdAt)
312
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
516
+ INSERT INTO iterations (id, projectId, name, goal, type, startDate, endDate, status, createdAt)
517
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
313
518
  `);
314
519
  stmt.run(
315
- newSprint.id,
316
- newSprint.projectId,
317
- newSprint.name,
318
- newSprint.goal || null,
319
- newSprint.startDate || null,
320
- newSprint.endDate || null,
321
- newSprint.status,
322
- newSprint.createdAt
520
+ newIteration.id,
521
+ newIteration.projectId,
522
+ newIteration.name,
523
+ newIteration.goal || null,
524
+ newIteration.type || null,
525
+ newIteration.startDate || null,
526
+ newIteration.endDate || null,
527
+ newIteration.status,
528
+ newIteration.createdAt
323
529
  );
324
- return newSprint;
530
+ return newIteration;
325
531
  }
326
532
 
327
- async updateSprint(id: string, updates: Partial<Sprint>): Promise<Sprint | null> {
328
- const stmt = this.db.prepare('SELECT * FROM sprints WHERE id = ?');
329
- const existingRow = stmt.get(id) as any;
330
- if (!existingRow) return null;
533
+ async updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null> {
534
+ const existing = await this.getIteration(id);
535
+ if (!existing) return null;
331
536
 
332
- const existing: Sprint = { ...existingRow, goal: existingRow.goal };
333
- const updated: Sprint = { ...existing, ...updates };
537
+ const updated: Iteration = { ...existing, ...updates };
334
538
 
335
539
  const updateStmt = this.db.prepare(`
336
- UPDATE sprints SET name = ?, goal = ?, startDate = ?, endDate = ?, status = ?
540
+ UPDATE iterations SET name = ?, goal = ?, type = ?, startDate = ?, endDate = ?, status = ?
337
541
  WHERE id = ?
338
542
  `);
339
543
  updateStmt.run(
340
544
  updated.name,
341
545
  updated.goal || null,
546
+ updated.type || null,
342
547
  updated.startDate || null,
343
548
  updated.endDate || null,
344
549
  updated.status,
@@ -347,6 +552,12 @@ export class SQLiteStore implements StorageAdapter {
347
552
  return updated;
348
553
  }
349
554
 
555
+ async deleteIteration(id: string): Promise<boolean> {
556
+ const stmt = this.db.prepare('DELETE FROM iterations WHERE id = ?');
557
+ const result = stmt.run(id);
558
+ return (result.changes ?? 0) > 0;
559
+ }
560
+
350
561
  // --- Comments & Activity ---
351
562
  async getComments(taskId: string): Promise<Comment[]> {
352
563
  const stmt = this.db.prepare('SELECT * FROM comments WHERE taskId = ?');
@@ -417,7 +628,8 @@ export class SQLiteStore implements StorageAdapter {
417
628
  // --- Time Tracking ---
418
629
  async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
419
630
  const stmt = this.db.prepare('SELECT * FROM time_entries WHERE taskId = ?');
420
- return stmt.all(taskId) as any[];
631
+ const rows = stmt.all(taskId) as any[];
632
+ return rows.map((r) => ({ ...r, isBillable: r.isBillable !== null ? Boolean(r.isBillable) : undefined }));
421
633
  }
422
634
 
423
635
  async logTime(entry: Omit<TimeEntry, 'id' | 'loggedAt'>): Promise<TimeEntry> {
@@ -426,10 +638,18 @@ export class SQLiteStore implements StorageAdapter {
426
638
  const newEntry: TimeEntry = { ...entry, id, loggedAt: now };
427
639
 
428
640
  const stmt = this.db.prepare(`
429
- INSERT INTO time_entries (id, taskId, userId, hours, description, loggedAt)
430
- VALUES (?, ?, ?, ?, ?, ?)
641
+ INSERT INTO time_entries (id, taskId, userId, hours, isBillable, description, loggedAt)
642
+ VALUES (?, ?, ?, ?, ?, ?, ?)
431
643
  `);
432
- stmt.run(newEntry.id, newEntry.taskId, newEntry.userId, newEntry.hours, newEntry.description || null, newEntry.loggedAt);
644
+ stmt.run(
645
+ newEntry.id,
646
+ newEntry.taskId,
647
+ newEntry.userId,
648
+ newEntry.hours,
649
+ newEntry.isBillable !== undefined ? (newEntry.isBillable ? 1 : 0) : null,
650
+ newEntry.description || null,
651
+ newEntry.loggedAt
652
+ );
433
653
  return newEntry;
434
654
  }
435
655
 
@@ -487,10 +707,20 @@ export class SQLiteStore implements StorageAdapter {
487
707
  return {
488
708
  ...row,
489
709
  members: row.members ? JSON.parse(row.members) : [],
710
+ teamIds: row.teamIds ? JSON.parse(row.teamIds) : [],
711
+ statusDefinitions: row.statusDefinitions ? JSON.parse(row.statusDefinitions) : [],
712
+ priorityDefinitions: row.priorityDefinitions ? JSON.parse(row.priorityDefinitions) : [],
490
713
  customFieldDefinitions: row.customFieldDefinitions ? JSON.parse(row.customFieldDefinitions) : []
491
714
  };
492
715
  }
493
716
 
717
+ private mapTeam(row: any): Team {
718
+ return {
719
+ ...row,
720
+ memberIds: row.memberIds ? JSON.parse(row.memberIds) : []
721
+ };
722
+ }
723
+
494
724
  private mapTask(row: any): Task {
495
725
  return {
496
726
  ...row,
@@ -1,6 +1,19 @@
1
- export type Priority = 'urgent' | 'high' | 'medium' | 'low' | 'none';
1
+ export type DefaultPriority = 'urgent' | 'high' | 'medium' | 'low' | 'none';
2
+ export type Priority = DefaultPriority | (string & {});
2
3
 
3
- export type TaskStatus = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'canceled';
4
+ export type DefaultTaskStatus = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'canceled';
5
+ export type TaskStatus = DefaultTaskStatus | (string & {});
6
+
7
+ export type CompletionState = 'done' | 'not_done';
8
+ export type ExecutionState = 'active' | 'inactive';
9
+
10
+ export interface StatusDefinition {
11
+ key: string;
12
+ label: string;
13
+ completionState: CompletionState;
14
+ executionState: ExecutionState;
15
+ isCancelled?: boolean;
16
+ }
4
17
 
5
18
  export type Role = 'admin' | 'project_manager' | 'contributor' | 'viewer';
6
19
 
@@ -13,6 +26,16 @@ export interface User {
13
26
  createdAt: string;
14
27
  }
15
28
 
29
+ export interface Team {
30
+ id: string;
31
+ name: string;
32
+ description?: string;
33
+ leaderId?: string;
34
+ memberIds: string[];
35
+ createdAt: string;
36
+ updatedAt: string;
37
+ }
38
+
16
39
  export interface CustomFieldDefinition {
17
40
  id: string;
18
41
  key: string;
@@ -25,16 +48,31 @@ export interface CustomFieldDefinition {
25
48
 
26
49
  export interface Project {
27
50
  id: string;
28
- key: string; // e.g. "CP" or "PROJ"
51
+ key?: string; // e.g. "CP" or "PROJ"
29
52
  name: string;
30
53
  description?: string;
31
54
  ownerId?: string;
32
55
  members?: string[]; // user IDs
56
+ teamIds?: string[]; // team IDs
57
+ statusDefinitions?: StatusDefinition[];
58
+ priorityDefinitions?: Array<{ key: string; label: string; level?: number }>;
33
59
  customFieldDefinitions?: CustomFieldDefinition[];
34
60
  createdAt: string;
35
61
  updatedAt: string;
36
62
  }
37
63
 
64
+ export interface TaskContainer {
65
+ id: string;
66
+ projectId: string;
67
+ name: string;
68
+ description?: string;
69
+ parentId?: string; // nested container hierarchy (e.g., folder -> epic -> group)
70
+ type?: 'epic' | 'group' | 'section' | 'folder' | string;
71
+ color?: string;
72
+ createdAt: string;
73
+ updatedAt: string;
74
+ }
75
+
38
76
  export interface TaskDependency {
39
77
  id: string;
40
78
  taskId: string;
@@ -42,19 +80,41 @@ export interface TaskDependency {
42
80
  type: 'blocking' | 'blocked_by' | 'relates_to';
43
81
  }
44
82
 
83
+ export interface TaskDependencyGraph {
84
+ taskId: string;
85
+ upstreamTasks: Task[]; // tasks that this task depends on
86
+ downstreamTasks: Task[]; // tasks that depend on this task
87
+ dependencies: TaskDependency[];
88
+ }
89
+
45
90
  export interface Task {
46
91
  id: string;
47
92
  projectId: string;
93
+ key?: string; // e.g. "MOB-12"
48
94
  title: string;
49
95
  description?: string;
50
96
  status: TaskStatus;
51
97
  priority: Priority;
52
98
  assigneeId?: string;
53
99
  reporterId?: string;
54
- sprintId?: string;
100
+ reviewerId?: string;
101
+ iterationId?: string;
102
+ teamId?: string;
103
+ containerId?: string;
104
+ plannedStartDate?: string;
105
+ actualStartDate?: string;
106
+ actualEndDate?: string;
55
107
  dueDate?: string;
108
+ // Duration & Effort (in hours and/or minutes)
56
109
  estimatedHours?: number;
57
110
  loggedHours?: number;
111
+ actualHours?: number;
112
+ billableHours?: number;
113
+ estimatedDurationMinutes?: number;
114
+ actualDurationMinutes?: number;
115
+ billableDurationMinutes?: number;
116
+ // Progress (0 to 100 percentage)
117
+ progress?: number;
58
118
  tags?: string[];
59
119
  customFields?: Record<string, unknown>;
60
120
  parentId?: string; // Subtask support
@@ -62,13 +122,14 @@ export interface Task {
62
122
  updatedAt: string;
63
123
  }
64
124
 
65
- export interface Sprint {
125
+ export interface Iteration {
66
126
  id: string;
67
127
  projectId: string;
68
128
  name: string;
69
129
  goal?: string;
70
- startDate: string;
71
- endDate: string;
130
+ type?: 'sprint' | 'cycle' | 'milestone' | string;
131
+ startDate?: string;
132
+ endDate?: string;
72
133
  status: 'planning' | 'active' | 'completed';
73
134
  createdAt: string;
74
135
  }
@@ -87,6 +148,7 @@ export interface TimeEntry {
87
148
  taskId: string;
88
149
  userId: string;
89
150
  hours: number;
151
+ isBillable?: boolean;
90
152
  description?: string;
91
153
  loggedAt: string;
92
154
  }
@@ -119,8 +181,10 @@ export type WebhookEvent =
119
181
  | 'task.deleted'
120
182
  | 'task.status_changed'
121
183
  | 'comment.created'
122
- | 'sprint.started'
123
- | 'sprint.completed';
184
+ | 'iteration.started'
185
+ | 'iteration.completed'
186
+ | 'team.created'
187
+ | 'container.created';
124
188
 
125
189
  export interface PluginHooks {
126
190
  beforeTaskCreate?: (task: Partial<Task>) => Promise<Partial<Task>> | Partial<Task>;
@@ -149,6 +213,8 @@ export interface CriticalPathConfig {
149
213
  projects?: Project[];
150
214
  tasks?: Task[];
151
215
  users?: User[];
152
- sprints?: Sprint[];
216
+ iterations?: Iteration[];
217
+ teams?: Team[];
218
+ containers?: TaskContainer[];
153
219
  };
154
220
  }
@@ -0,0 +1,2 @@
1
+ export * from './status.js';
2
+ export * from './key.js';