@critical-path/core 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +6 -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 +95 -13
  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 +76 -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 +176 -38
  25. package/dist/store/sqlite.js.map +1 -1
  26. package/dist/types/index.d.ts +68 -8
  27. package/dist/types/index.d.ts.map +1 -1
  28. package/dist/utils/status.d.ts +14 -0
  29. package/dist/utils/status.d.ts.map +1 -0
  30. package/dist/utils/status.js +58 -0
  31. package/dist/utils/status.js.map +1 -0
  32. package/package.json +5 -1
  33. package/src/engine/index.ts +161 -8
  34. package/src/index.test.ts +113 -1
  35. package/src/index.ts +1 -0
  36. package/src/store/firebase.test.ts +8 -2
  37. package/src/store/firebase.ts +109 -17
  38. package/src/store/index.ts +110 -18
  39. package/src/store/sqlite.ts +272 -44
  40. package/src/types/index.ts +74 -9
  41. package/src/utils/status.ts +85 -0
  42. package/tsconfig.tsbuildinfo +1 -1
@@ -4,7 +4,9 @@ export * from './firebase.js';
4
4
  import type {
5
5
  Project,
6
6
  Task,
7
- Sprint,
7
+ Iteration,
8
+ Team,
9
+ TaskContainer,
8
10
  Comment,
9
11
  TimeEntry,
10
12
  Activity,
@@ -27,10 +29,26 @@ export interface StorageAdapter {
27
29
  updateTask(id: string, updates: Partial<Task>): Promise<Task | null>;
28
30
  deleteTask(id: string): Promise<boolean>;
29
31
 
30
- // Sprints
31
- getSprints(projectId: string): Promise<Sprint[]>;
32
- createSprint(sprint: Omit<Sprint, 'id' | 'createdAt'>): Promise<Sprint>;
33
- updateSprint(id: string, updates: Partial<Sprint>): Promise<Sprint | null>;
32
+ // Teams
33
+ getTeams(): Promise<Team[]>;
34
+ getTeam(id: string): Promise<Team | null>;
35
+ createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team>;
36
+ updateTeam(id: string, updates: Partial<Team>): Promise<Team | null>;
37
+ deleteTeam(id: string): Promise<boolean>;
38
+
39
+ // Task Containers
40
+ getContainers(projectId: string): Promise<TaskContainer[]>;
41
+ getContainer(id: string): Promise<TaskContainer | null>;
42
+ createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer>;
43
+ updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null>;
44
+ deleteContainer(id: string): Promise<boolean>;
45
+
46
+ // Iterations
47
+ getIterations(projectId: string): Promise<Iteration[]>;
48
+ getIteration(id: string): Promise<Iteration | null>;
49
+ createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration>;
50
+ updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null>;
51
+ deleteIteration(id: string): Promise<boolean>;
34
52
 
35
53
  // Comments & Activity
36
54
  getComments(taskId: string): Promise<Comment[]>;
@@ -54,7 +72,9 @@ export interface StorageAdapter {
54
72
  export class InMemoryStore implements StorageAdapter {
55
73
  private projects = new Map<string, Project>();
56
74
  private tasks = new Map<string, Task>();
57
- private sprints = new Map<string, Sprint>();
75
+ private teams = new Map<string, Team>();
76
+ private containers = new Map<string, TaskContainer>();
77
+ private iterations = new Map<string, Iteration>();
58
78
  private comments = new Map<string, Comment>();
59
79
  private activities: Activity[] = [];
60
80
  private timeEntries = new Map<string, TimeEntry>();
@@ -139,29 +159,97 @@ export class InMemoryStore implements StorageAdapter {
139
159
  return this.tasks.delete(id);
140
160
  }
141
161
 
142
- async getSprints(projectId: string): Promise<Sprint[]> {
143
- return Array.from(this.sprints.values()).filter((s) => s.projectId === projectId);
162
+ // Teams
163
+ async getTeams(): Promise<Team[]> {
164
+ return Array.from(this.teams.values());
144
165
  }
145
166
 
146
- async createSprint(sprint: Omit<Sprint, 'id' | 'createdAt'>): Promise<Sprint> {
147
- const id = `sprint_${Math.random().toString(36).substring(2, 9)}`;
148
- const newSprint: Sprint = {
149
- ...sprint,
167
+ async getTeam(id: string): Promise<Team | null> {
168
+ return this.teams.get(id) || null;
169
+ }
170
+
171
+ async createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team> {
172
+ const id = `team_${Math.random().toString(36).substring(2, 9)}`;
173
+ const now = new Date().toISOString();
174
+ const newTeam: Team = { ...team, id, createdAt: now, updatedAt: now };
175
+ this.teams.set(id, newTeam);
176
+ return newTeam;
177
+ }
178
+
179
+ async updateTeam(id: string, updates: Partial<Team>): Promise<Team | null> {
180
+ const existing = this.teams.get(id);
181
+ if (!existing) return null;
182
+ const updated: Team = { ...existing, ...updates, updatedAt: new Date().toISOString() };
183
+ this.teams.set(id, updated);
184
+ return updated;
185
+ }
186
+
187
+ async deleteTeam(id: string): Promise<boolean> {
188
+ return this.teams.delete(id);
189
+ }
190
+
191
+ // Containers
192
+ async getContainers(projectId: string): Promise<TaskContainer[]> {
193
+ return Array.from(this.containers.values()).filter((c) => c.projectId === projectId);
194
+ }
195
+
196
+ async getContainer(id: string): Promise<TaskContainer | null> {
197
+ return this.containers.get(id) || null;
198
+ }
199
+
200
+ async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
201
+ const id = `cnt_${Math.random().toString(36).substring(2, 9)}`;
202
+ const now = new Date().toISOString();
203
+ const newContainer: TaskContainer = { ...container, id, createdAt: now, updatedAt: now };
204
+ this.containers.set(id, newContainer);
205
+ return newContainer;
206
+ }
207
+
208
+ async updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null> {
209
+ const existing = this.containers.get(id);
210
+ if (!existing) return null;
211
+ const updated: TaskContainer = { ...existing, ...updates, updatedAt: new Date().toISOString() };
212
+ this.containers.set(id, updated);
213
+ return updated;
214
+ }
215
+
216
+ async deleteContainer(id: string): Promise<boolean> {
217
+ return this.containers.delete(id);
218
+ }
219
+
220
+ // Iterations
221
+ async getIterations(projectId: string): Promise<Iteration[]> {
222
+ return Array.from(this.iterations.values()).filter((s) => s.projectId === projectId);
223
+ }
224
+
225
+ async getIteration(id: string): Promise<Iteration | null> {
226
+ return this.iterations.get(id) || null;
227
+ }
228
+
229
+ async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
230
+ const id = `iter_${Math.random().toString(36).substring(2, 9)}`;
231
+ const newIteration: Iteration = {
232
+ ...iteration,
150
233
  id,
151
234
  createdAt: new Date().toISOString()
152
235
  };
153
- this.sprints.set(id, newSprint);
154
- return newSprint;
236
+ this.iterations.set(id, newIteration);
237
+ return newIteration;
155
238
  }
156
239
 
157
- async updateSprint(id: string, updates: Partial<Sprint>): Promise<Sprint | null> {
158
- const existing = this.sprints.get(id);
240
+ async updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null> {
241
+ const existing = this.iterations.get(id);
159
242
  if (!existing) return null;
160
- const updated: Sprint = { ...existing, ...updates };
161
- this.sprints.set(id, updated);
243
+ const updated: Iteration = { ...existing, ...updates };
244
+ this.iterations.set(id, updated);
162
245
  return updated;
163
246
  }
164
247
 
248
+ async deleteIteration(id: string): Promise<boolean> {
249
+ return this.iterations.delete(id);
250
+ }
251
+
252
+ // Comments
165
253
  async getComments(taskId: string): Promise<Comment[]> {
166
254
  return Array.from(this.comments.values()).filter((c) => c.taskId === taskId);
167
255
  }
@@ -174,6 +262,7 @@ export class InMemoryStore implements StorageAdapter {
174
262
  return newComment;
175
263
  }
176
264
 
265
+ // Activities
177
266
  async getActivities(filter?: { projectId?: string; taskId?: string }): Promise<Activity[]> {
178
267
  return this.activities.filter((a) => {
179
268
  if (filter?.projectId && a.projectId !== filter.projectId) return false;
@@ -189,6 +278,7 @@ export class InMemoryStore implements StorageAdapter {
189
278
  return newAct;
190
279
  }
191
280
 
281
+ // Time Entries
192
282
  async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
193
283
  return Array.from(this.timeEntries.values()).filter((e) => e.taskId === taskId);
194
284
  }
@@ -200,6 +290,7 @@ export class InMemoryStore implements StorageAdapter {
200
290
  return newEntry;
201
291
  }
202
292
 
293
+ // Dependencies
203
294
  async getDependencies(taskId: string): Promise<TaskDependency[]> {
204
295
  return Array.from(this.dependencies.values()).filter(
205
296
  (d) => d.taskId === taskId || d.dependsOnTaskId === taskId
@@ -213,6 +304,7 @@ export class InMemoryStore implements StorageAdapter {
213
304
  return newDep;
214
305
  }
215
306
 
307
+ // Webhooks
216
308
  async getWebhooks(): Promise<Webhook[]> {
217
309
  return Array.from(this.webhooks.values());
218
310
  }
@@ -3,7 +3,9 @@ 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,
@@ -44,11 +46,36 @@ export class SQLiteStore implements StorageAdapter {
44
46
  description TEXT,
45
47
  ownerId TEXT,
46
48
  members TEXT,
49
+ teamIds TEXT,
50
+ statusDefinitions TEXT,
51
+ priorityDefinitions TEXT,
47
52
  customFieldDefinitions TEXT,
48
53
  createdAt TEXT NOT NULL,
49
54
  updatedAt TEXT NOT NULL
50
55
  );
51
56
 
57
+ CREATE TABLE IF NOT EXISTS teams (
58
+ id TEXT PRIMARY KEY,
59
+ name TEXT NOT NULL,
60
+ description TEXT,
61
+ leaderId TEXT,
62
+ memberIds TEXT NOT NULL,
63
+ createdAt TEXT NOT NULL,
64
+ updatedAt TEXT NOT NULL
65
+ );
66
+
67
+ CREATE TABLE IF NOT EXISTS containers (
68
+ id TEXT PRIMARY KEY,
69
+ projectId TEXT NOT NULL,
70
+ name TEXT NOT NULL,
71
+ description TEXT,
72
+ parentId TEXT,
73
+ type TEXT,
74
+ color TEXT,
75
+ createdAt TEXT NOT NULL,
76
+ updatedAt TEXT NOT NULL
77
+ );
78
+
52
79
  CREATE TABLE IF NOT EXISTS tasks (
53
80
  id TEXT PRIMARY KEY,
54
81
  projectId TEXT NOT NULL,
@@ -58,10 +85,22 @@ export class SQLiteStore implements StorageAdapter {
58
85
  priority TEXT NOT NULL,
59
86
  assigneeId TEXT,
60
87
  reporterId TEXT,
61
- sprintId TEXT,
88
+ reviewerId TEXT,
89
+ iterationId TEXT,
90
+ teamId TEXT,
91
+ containerId TEXT,
92
+ plannedStartDate TEXT,
93
+ actualStartDate TEXT,
94
+ actualEndDate TEXT,
62
95
  dueDate TEXT,
63
96
  estimatedHours REAL,
64
97
  loggedHours REAL,
98
+ actualHours REAL,
99
+ billableHours REAL,
100
+ estimatedDurationMinutes REAL,
101
+ actualDurationMinutes REAL,
102
+ billableDurationMinutes REAL,
103
+ progress REAL,
65
104
  tags TEXT,
66
105
  customFields TEXT,
67
106
  parentId TEXT,
@@ -69,11 +108,12 @@ export class SQLiteStore implements StorageAdapter {
69
108
  updatedAt TEXT NOT NULL
70
109
  );
71
110
 
72
- CREATE TABLE IF NOT EXISTS sprints (
111
+ CREATE TABLE IF NOT EXISTS iterations (
73
112
  id TEXT PRIMARY KEY,
74
113
  projectId TEXT NOT NULL,
75
114
  name TEXT NOT NULL,
76
115
  goal TEXT,
116
+ type TEXT,
77
117
  startDate TEXT,
78
118
  endDate TEXT,
79
119
  status TEXT NOT NULL,
@@ -104,6 +144,7 @@ export class SQLiteStore implements StorageAdapter {
104
144
  taskId TEXT NOT NULL,
105
145
  userId TEXT NOT NULL,
106
146
  hours REAL NOT NULL,
147
+ isBillable INTEGER,
107
148
  description TEXT,
108
149
  loggedAt TEXT NOT NULL
109
150
  );
@@ -145,8 +186,8 @@ export class SQLiteStore implements StorageAdapter {
145
186
  const newProj: Project = { ...project, id, createdAt: now, updatedAt: now };
146
187
 
147
188
  const stmt = this.db.prepare(`
148
- INSERT INTO projects (id, key, name, description, ownerId, members, customFieldDefinitions, createdAt, updatedAt)
149
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
189
+ INSERT INTO projects (id, key, name, description, ownerId, members, teamIds, statusDefinitions, priorityDefinitions, customFieldDefinitions, createdAt, updatedAt)
190
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
150
191
  `);
151
192
  stmt.run(
152
193
  newProj.id,
@@ -155,6 +196,9 @@ export class SQLiteStore implements StorageAdapter {
155
196
  newProj.description || null,
156
197
  newProj.ownerId || null,
157
198
  JSON.stringify(newProj.members || []),
199
+ JSON.stringify(newProj.teamIds || []),
200
+ JSON.stringify(newProj.statusDefinitions || []),
201
+ JSON.stringify(newProj.priorityDefinitions || []),
158
202
  JSON.stringify(newProj.customFieldDefinitions || []),
159
203
  newProj.createdAt,
160
204
  newProj.updatedAt
@@ -174,7 +218,7 @@ export class SQLiteStore implements StorageAdapter {
174
218
 
175
219
  const stmt = this.db.prepare(`
176
220
  UPDATE projects
177
- SET key = ?, name = ?, description = ?, ownerId = ?, members = ?, customFieldDefinitions = ?, updatedAt = ?
221
+ SET key = ?, name = ?, description = ?, ownerId = ?, members = ?, teamIds = ?, statusDefinitions = ?, priorityDefinitions = ?, customFieldDefinitions = ?, updatedAt = ?
178
222
  WHERE id = ?
179
223
  `);
180
224
  stmt.run(
@@ -183,6 +227,9 @@ export class SQLiteStore implements StorageAdapter {
183
227
  updated.description || null,
184
228
  updated.ownerId || null,
185
229
  JSON.stringify(updated.members || []),
230
+ JSON.stringify(updated.teamIds || []),
231
+ JSON.stringify(updated.statusDefinitions || []),
232
+ JSON.stringify(updated.priorityDefinitions || []),
186
233
  JSON.stringify(updated.customFieldDefinitions || []),
187
234
  updated.updatedAt,
188
235
  id
@@ -196,6 +243,129 @@ export class SQLiteStore implements StorageAdapter {
196
243
  return (result.changes ?? 0) > 0;
197
244
  }
198
245
 
246
+ // --- Teams ---
247
+ async getTeams(): Promise<Team[]> {
248
+ const stmt = this.db.prepare('SELECT * FROM teams');
249
+ const rows = stmt.all() as any[];
250
+ return rows.map((r) => this.mapTeam(r));
251
+ }
252
+
253
+ async getTeam(id: string): Promise<Team | null> {
254
+ const stmt = this.db.prepare('SELECT * FROM teams WHERE id = ?');
255
+ const row = stmt.get(id) as any;
256
+ return row ? this.mapTeam(row) : null;
257
+ }
258
+
259
+ async createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team> {
260
+ const id = `team_${Math.random().toString(36).substring(2, 9)}`;
261
+ const now = new Date().toISOString();
262
+ const newTeam: Team = { ...team, id, createdAt: now, updatedAt: now };
263
+
264
+ const stmt = this.db.prepare(`
265
+ INSERT INTO teams (id, name, description, leaderId, memberIds, createdAt, updatedAt)
266
+ VALUES (?, ?, ?, ?, ?, ?, ?)
267
+ `);
268
+ stmt.run(
269
+ newTeam.id,
270
+ newTeam.name,
271
+ newTeam.description || null,
272
+ newTeam.leaderId || null,
273
+ JSON.stringify(newTeam.memberIds || []),
274
+ newTeam.createdAt,
275
+ newTeam.updatedAt
276
+ );
277
+ return newTeam;
278
+ }
279
+
280
+ async updateTeam(id: string, updates: Partial<Team>): Promise<Team | null> {
281
+ const existing = await this.getTeam(id);
282
+ if (!existing) return null;
283
+
284
+ const updated: Team = { ...existing, ...updates, updatedAt: new Date().toISOString() };
285
+ const stmt = this.db.prepare(`
286
+ UPDATE teams SET name = ?, description = ?, leaderId = ?, memberIds = ?, updatedAt = ?
287
+ WHERE id = ?
288
+ `);
289
+ stmt.run(
290
+ updated.name,
291
+ updated.description || null,
292
+ updated.leaderId || null,
293
+ JSON.stringify(updated.memberIds || []),
294
+ updated.updatedAt,
295
+ id
296
+ );
297
+ return updated;
298
+ }
299
+
300
+ async deleteTeam(id: string): Promise<boolean> {
301
+ const stmt = this.db.prepare('DELETE FROM teams WHERE id = ?');
302
+ const result = stmt.run(id);
303
+ return (result.changes ?? 0) > 0;
304
+ }
305
+
306
+ // --- Task Containers ---
307
+ async getContainers(projectId: string): Promise<TaskContainer[]> {
308
+ const stmt = this.db.prepare('SELECT * FROM containers WHERE projectId = ?');
309
+ const rows = stmt.all(projectId) as any[];
310
+ return rows;
311
+ }
312
+
313
+ async getContainer(id: string): Promise<TaskContainer | null> {
314
+ const stmt = this.db.prepare('SELECT * FROM containers WHERE id = ?');
315
+ const row = stmt.get(id) as any;
316
+ return row || null;
317
+ }
318
+
319
+ async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
320
+ const id = `cnt_${Math.random().toString(36).substring(2, 9)}`;
321
+ const now = new Date().toISOString();
322
+ const newContainer: TaskContainer = { ...container, id, createdAt: now, updatedAt: now };
323
+
324
+ const stmt = this.db.prepare(`
325
+ INSERT INTO containers (id, projectId, name, description, parentId, type, color, createdAt, updatedAt)
326
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
327
+ `);
328
+ stmt.run(
329
+ newContainer.id,
330
+ newContainer.projectId,
331
+ newContainer.name,
332
+ newContainer.description || null,
333
+ newContainer.parentId || null,
334
+ newContainer.type || null,
335
+ newContainer.color || null,
336
+ newContainer.createdAt,
337
+ newContainer.updatedAt
338
+ );
339
+ return newContainer;
340
+ }
341
+
342
+ async updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null> {
343
+ const existing = await this.getContainer(id);
344
+ if (!existing) return null;
345
+
346
+ const updated: TaskContainer = { ...existing, ...updates, updatedAt: new Date().toISOString() };
347
+ const stmt = this.db.prepare(`
348
+ UPDATE containers SET name = ?, description = ?, parentId = ?, type = ?, color = ?, updatedAt = ?
349
+ WHERE id = ?
350
+ `);
351
+ stmt.run(
352
+ updated.name,
353
+ updated.description || null,
354
+ updated.parentId || null,
355
+ updated.type || null,
356
+ updated.color || null,
357
+ updated.updatedAt,
358
+ id
359
+ );
360
+ return updated;
361
+ }
362
+
363
+ async deleteContainer(id: string): Promise<boolean> {
364
+ const stmt = this.db.prepare('DELETE FROM containers WHERE id = ?');
365
+ const result = stmt.run(id);
366
+ return (result.changes ?? 0) > 0;
367
+ }
368
+
199
369
  // --- Tasks ---
200
370
  async getTasks(projectId?: string): Promise<Task[]> {
201
371
  let rows: any[];
@@ -223,8 +393,11 @@ export class SQLiteStore implements StorageAdapter {
223
393
  const stmt = this.db.prepare(`
224
394
  INSERT INTO tasks (
225
395
  id, projectId, title, description, status, priority, assigneeId, reporterId,
226
- sprintId, dueDate, estimatedHours, loggedHours, tags, customFields, parentId, createdAt, updatedAt
227
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
396
+ reviewerId, iterationId, teamId, containerId, plannedStartDate, actualStartDate, actualEndDate,
397
+ dueDate, estimatedHours, loggedHours, actualHours, billableHours,
398
+ estimatedDurationMinutes, actualDurationMinutes, billableDurationMinutes, progress,
399
+ tags, customFields, parentId, createdAt, updatedAt
400
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
228
401
  `);
229
402
  stmt.run(
230
403
  newTask.id,
@@ -235,10 +408,22 @@ export class SQLiteStore implements StorageAdapter {
235
408
  newTask.priority,
236
409
  newTask.assigneeId || null,
237
410
  newTask.reporterId || null,
238
- newTask.sprintId || null,
411
+ newTask.reviewerId || null,
412
+ newTask.iterationId || null,
413
+ newTask.teamId || null,
414
+ newTask.containerId || null,
415
+ newTask.plannedStartDate || null,
416
+ newTask.actualStartDate || null,
417
+ newTask.actualEndDate || null,
239
418
  newTask.dueDate || null,
240
419
  newTask.estimatedHours ?? null,
241
420
  newTask.loggedHours ?? null,
421
+ newTask.actualHours ?? null,
422
+ newTask.billableHours ?? null,
423
+ newTask.estimatedDurationMinutes ?? null,
424
+ newTask.actualDurationMinutes ?? null,
425
+ newTask.billableDurationMinutes ?? null,
426
+ newTask.progress ?? null,
242
427
  JSON.stringify(newTask.tags || []),
243
428
  JSON.stringify(newTask.customFields || {}),
244
429
  newTask.parentId || null,
@@ -261,8 +446,11 @@ export class SQLiteStore implements StorageAdapter {
261
446
  const stmt = this.db.prepare(`
262
447
  UPDATE tasks SET
263
448
  projectId = ?, title = ?, description = ?, status = ?, priority = ?,
264
- assigneeId = ?, reporterId = ?, sprintId = ?, dueDate = ?,
265
- estimatedHours = ?, loggedHours = ?, tags = ?, customFields = ?, parentId = ?, updatedAt = ?
449
+ assigneeId = ?, reporterId = ?, reviewerId = ?, iterationId = ?, teamId = ?, containerId = ?,
450
+ plannedStartDate = ?, actualStartDate = ?, actualEndDate = ?, dueDate = ?,
451
+ estimatedHours = ?, loggedHours = ?, actualHours = ?, billableHours = ?,
452
+ estimatedDurationMinutes = ?, actualDurationMinutes = ?, billableDurationMinutes = ?, progress = ?,
453
+ tags = ?, customFields = ?, parentId = ?, updatedAt = ?
266
454
  WHERE id = ?
267
455
  `);
268
456
  stmt.run(
@@ -273,10 +461,22 @@ export class SQLiteStore implements StorageAdapter {
273
461
  updated.priority,
274
462
  updated.assigneeId || null,
275
463
  updated.reporterId || null,
276
- updated.sprintId || null,
464
+ updated.reviewerId || null,
465
+ updated.iterationId || null,
466
+ updated.teamId || null,
467
+ updated.containerId || null,
468
+ updated.plannedStartDate || null,
469
+ updated.actualStartDate || null,
470
+ updated.actualEndDate || null,
277
471
  updated.dueDate || null,
278
472
  updated.estimatedHours ?? null,
279
473
  updated.loggedHours ?? null,
474
+ updated.actualHours ?? null,
475
+ updated.billableHours ?? null,
476
+ updated.estimatedDurationMinutes ?? null,
477
+ updated.actualDurationMinutes ?? null,
478
+ updated.billableDurationMinutes ?? null,
479
+ updated.progress ?? null,
280
480
  JSON.stringify(updated.tags || []),
281
481
  JSON.stringify(updated.customFields || {}),
282
482
  updated.parentId || null,
@@ -292,53 +492,56 @@ export class SQLiteStore implements StorageAdapter {
292
492
  return (result.changes ?? 0) > 0;
293
493
  }
294
494
 
295
- // --- Sprints ---
296
- async getSprints(projectId: string): Promise<Sprint[]> {
297
- const stmt = this.db.prepare('SELECT * FROM sprints WHERE projectId = ?');
495
+ // --- Iterations ---
496
+ async getIterations(projectId: string): Promise<Iteration[]> {
497
+ const stmt = this.db.prepare('SELECT * FROM iterations WHERE projectId = ?');
298
498
  const rows = stmt.all(projectId) as any[];
299
- return rows.map((r) => ({
300
- ...r,
301
- description: r.goal
302
- }));
499
+ return rows;
303
500
  }
304
501
 
305
- async createSprint(sprint: Omit<Sprint, 'id' | 'createdAt'>): Promise<Sprint> {
306
- const id = `sprint_${Math.random().toString(36).substring(2, 9)}`;
502
+ async getIteration(id: string): Promise<Iteration | null> {
503
+ const stmt = this.db.prepare('SELECT * FROM iterations WHERE id = ?');
504
+ const row = stmt.get(id) as any;
505
+ return row || null;
506
+ }
507
+
508
+ async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
509
+ const id = `iter_${Math.random().toString(36).substring(2, 9)}`;
307
510
  const now = new Date().toISOString();
308
- const newSprint: Sprint = { ...sprint, id, createdAt: now };
511
+ const newIteration: Iteration = { ...iteration, id, createdAt: now };
309
512
 
310
513
  const stmt = this.db.prepare(`
311
- INSERT INTO sprints (id, projectId, name, goal, startDate, endDate, status, createdAt)
312
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
514
+ INSERT INTO iterations (id, projectId, name, goal, type, startDate, endDate, status, createdAt)
515
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
313
516
  `);
314
517
  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
518
+ newIteration.id,
519
+ newIteration.projectId,
520
+ newIteration.name,
521
+ newIteration.goal || null,
522
+ newIteration.type || null,
523
+ newIteration.startDate || null,
524
+ newIteration.endDate || null,
525
+ newIteration.status,
526
+ newIteration.createdAt
323
527
  );
324
- return newSprint;
528
+ return newIteration;
325
529
  }
326
530
 
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;
531
+ async updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null> {
532
+ const existing = await this.getIteration(id);
533
+ if (!existing) return null;
331
534
 
332
- const existing: Sprint = { ...existingRow, goal: existingRow.goal };
333
- const updated: Sprint = { ...existing, ...updates };
535
+ const updated: Iteration = { ...existing, ...updates };
334
536
 
335
537
  const updateStmt = this.db.prepare(`
336
- UPDATE sprints SET name = ?, goal = ?, startDate = ?, endDate = ?, status = ?
538
+ UPDATE iterations SET name = ?, goal = ?, type = ?, startDate = ?, endDate = ?, status = ?
337
539
  WHERE id = ?
338
540
  `);
339
541
  updateStmt.run(
340
542
  updated.name,
341
543
  updated.goal || null,
544
+ updated.type || null,
342
545
  updated.startDate || null,
343
546
  updated.endDate || null,
344
547
  updated.status,
@@ -347,6 +550,12 @@ export class SQLiteStore implements StorageAdapter {
347
550
  return updated;
348
551
  }
349
552
 
553
+ async deleteIteration(id: string): Promise<boolean> {
554
+ const stmt = this.db.prepare('DELETE FROM iterations WHERE id = ?');
555
+ const result = stmt.run(id);
556
+ return (result.changes ?? 0) > 0;
557
+ }
558
+
350
559
  // --- Comments & Activity ---
351
560
  async getComments(taskId: string): Promise<Comment[]> {
352
561
  const stmt = this.db.prepare('SELECT * FROM comments WHERE taskId = ?');
@@ -417,7 +626,8 @@ export class SQLiteStore implements StorageAdapter {
417
626
  // --- Time Tracking ---
418
627
  async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
419
628
  const stmt = this.db.prepare('SELECT * FROM time_entries WHERE taskId = ?');
420
- return stmt.all(taskId) as any[];
629
+ const rows = stmt.all(taskId) as any[];
630
+ return rows.map((r) => ({ ...r, isBillable: r.isBillable !== null ? Boolean(r.isBillable) : undefined }));
421
631
  }
422
632
 
423
633
  async logTime(entry: Omit<TimeEntry, 'id' | 'loggedAt'>): Promise<TimeEntry> {
@@ -426,10 +636,18 @@ export class SQLiteStore implements StorageAdapter {
426
636
  const newEntry: TimeEntry = { ...entry, id, loggedAt: now };
427
637
 
428
638
  const stmt = this.db.prepare(`
429
- INSERT INTO time_entries (id, taskId, userId, hours, description, loggedAt)
430
- VALUES (?, ?, ?, ?, ?, ?)
639
+ INSERT INTO time_entries (id, taskId, userId, hours, isBillable, description, loggedAt)
640
+ VALUES (?, ?, ?, ?, ?, ?, ?)
431
641
  `);
432
- stmt.run(newEntry.id, newEntry.taskId, newEntry.userId, newEntry.hours, newEntry.description || null, newEntry.loggedAt);
642
+ stmt.run(
643
+ newEntry.id,
644
+ newEntry.taskId,
645
+ newEntry.userId,
646
+ newEntry.hours,
647
+ newEntry.isBillable !== undefined ? (newEntry.isBillable ? 1 : 0) : null,
648
+ newEntry.description || null,
649
+ newEntry.loggedAt
650
+ );
433
651
  return newEntry;
434
652
  }
435
653
 
@@ -487,10 +705,20 @@ export class SQLiteStore implements StorageAdapter {
487
705
  return {
488
706
  ...row,
489
707
  members: row.members ? JSON.parse(row.members) : [],
708
+ teamIds: row.teamIds ? JSON.parse(row.teamIds) : [],
709
+ statusDefinitions: row.statusDefinitions ? JSON.parse(row.statusDefinitions) : [],
710
+ priorityDefinitions: row.priorityDefinitions ? JSON.parse(row.priorityDefinitions) : [],
490
711
  customFieldDefinitions: row.customFieldDefinitions ? JSON.parse(row.customFieldDefinitions) : []
491
712
  };
492
713
  }
493
714
 
715
+ private mapTeam(row: any): Team {
716
+ return {
717
+ ...row,
718
+ memberIds: row.memberIds ? JSON.parse(row.memberIds) : []
719
+ };
720
+ }
721
+
494
722
  private mapTask(row: any): Task {
495
723
  return {
496
724
  ...row,