@critical-path/core 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/engine/index.d.ts +8 -1
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +90 -2
- package/dist/engine/index.js.map +1 -1
- package/dist/index.test.js +44 -0
- package/dist/index.test.js.map +1 -1
- package/dist/store/firebase.d.ts +6 -1
- package/dist/store/firebase.d.ts.map +1 -1
- package/dist/store/firebase.js +37 -0
- package/dist/store/firebase.js.map +1 -1
- package/dist/store/firebase.test.js +16 -0
- package/dist/store/firebase.test.js.map +1 -1
- package/dist/store/index.d.ts +12 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +35 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/sqlite.d.ts +7 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +81 -10
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/sqlite.test.js +21 -0
- package/dist/store/sqlite.test.js.map +1 -1
- package/dist/types/index.d.ts +32 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/workflow.d.ts +13 -0
- package/dist/utils/workflow.d.ts.map +1 -0
- package/dist/utils/workflow.js +101 -0
- package/dist/utils/workflow.js.map +1 -0
- package/package.json +1 -1
- package/src/engine/index.ts +103 -3
- package/src/index.test.ts +54 -0
- package/src/store/firebase.test.ts +21 -0
- package/src/store/firebase.ts +42 -1
- package/src/store/index.ts +48 -1
- package/src/store/sqlite.test.ts +28 -0
- package/src/store/sqlite.ts +114 -7
- package/src/types/index.ts +38 -1
- package/src/utils/index.ts +2 -0
- package/src/utils/workflow.ts +128 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/store/sqlite.ts
CHANGED
|
@@ -10,7 +10,8 @@ import type {
|
|
|
10
10
|
TimeEntry,
|
|
11
11
|
Activity,
|
|
12
12
|
Webhook,
|
|
13
|
-
TaskDependency
|
|
13
|
+
TaskDependency,
|
|
14
|
+
Workflow
|
|
14
15
|
} from '../types/index.js';
|
|
15
16
|
import { generateProjectKey } from '../utils/key.js';
|
|
16
17
|
|
|
@@ -48,6 +49,8 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
48
49
|
ownerId TEXT,
|
|
49
50
|
members TEXT,
|
|
50
51
|
teamIds TEXT,
|
|
52
|
+
workflowId TEXT,
|
|
53
|
+
taskTypes TEXT,
|
|
51
54
|
statusDefinitions TEXT,
|
|
52
55
|
priorityDefinitions TEXT,
|
|
53
56
|
customFieldDefinitions TEXT,
|
|
@@ -55,6 +58,19 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
55
58
|
updatedAt TEXT NOT NULL
|
|
56
59
|
);
|
|
57
60
|
|
|
61
|
+
CREATE TABLE IF NOT EXISTS workflows (
|
|
62
|
+
id TEXT PRIMARY KEY,
|
|
63
|
+
name TEXT NOT NULL,
|
|
64
|
+
description TEXT,
|
|
65
|
+
statuses TEXT NOT NULL,
|
|
66
|
+
transitions TEXT NOT NULL,
|
|
67
|
+
taskTypes TEXT,
|
|
68
|
+
defaultStatusKey TEXT,
|
|
69
|
+
isDefault INTEGER,
|
|
70
|
+
createdAt TEXT NOT NULL,
|
|
71
|
+
updatedAt TEXT NOT NULL
|
|
72
|
+
);
|
|
73
|
+
|
|
58
74
|
CREATE TABLE IF NOT EXISTS teams (
|
|
59
75
|
id TEXT PRIMARY KEY,
|
|
60
76
|
name TEXT NOT NULL,
|
|
@@ -84,6 +100,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
84
100
|
description TEXT,
|
|
85
101
|
status TEXT NOT NULL,
|
|
86
102
|
priority TEXT NOT NULL,
|
|
103
|
+
taskType TEXT,
|
|
87
104
|
assigneeId TEXT,
|
|
88
105
|
reporterId TEXT,
|
|
89
106
|
reviewerId TEXT,
|
|
@@ -188,8 +205,8 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
188
205
|
const newProj: Project = { ...project, key, id, createdAt: now, updatedAt: now };
|
|
189
206
|
|
|
190
207
|
const stmt = this.db.prepare(`
|
|
191
|
-
INSERT INTO projects (id, key, name, description, ownerId, members, teamIds, statusDefinitions, priorityDefinitions, customFieldDefinitions, createdAt, updatedAt)
|
|
192
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
208
|
+
INSERT INTO projects (id, key, name, description, ownerId, members, teamIds, workflowId, taskTypes, statusDefinitions, priorityDefinitions, customFieldDefinitions, createdAt, updatedAt)
|
|
209
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
193
210
|
`);
|
|
194
211
|
stmt.run(
|
|
195
212
|
newProj.id,
|
|
@@ -199,6 +216,8 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
199
216
|
newProj.ownerId || null,
|
|
200
217
|
JSON.stringify(newProj.members || []),
|
|
201
218
|
JSON.stringify(newProj.teamIds || []),
|
|
219
|
+
newProj.workflowId || null,
|
|
220
|
+
newProj.taskTypes ? JSON.stringify(newProj.taskTypes) : null,
|
|
202
221
|
JSON.stringify(newProj.statusDefinitions || []),
|
|
203
222
|
JSON.stringify(newProj.priorityDefinitions || []),
|
|
204
223
|
JSON.stringify(newProj.customFieldDefinitions || []),
|
|
@@ -220,7 +239,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
220
239
|
|
|
221
240
|
const stmt = this.db.prepare(`
|
|
222
241
|
UPDATE projects
|
|
223
|
-
SET key = ?, name = ?, description = ?, ownerId = ?, members = ?, teamIds = ?, statusDefinitions = ?, priorityDefinitions = ?, customFieldDefinitions = ?, updatedAt = ?
|
|
242
|
+
SET key = ?, name = ?, description = ?, ownerId = ?, members = ?, teamIds = ?, workflowId = ?, taskTypes = ?, statusDefinitions = ?, priorityDefinitions = ?, customFieldDefinitions = ?, updatedAt = ?
|
|
224
243
|
WHERE id = ?
|
|
225
244
|
`);
|
|
226
245
|
stmt.run(
|
|
@@ -230,6 +249,8 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
230
249
|
updated.ownerId || null,
|
|
231
250
|
JSON.stringify(updated.members || []),
|
|
232
251
|
JSON.stringify(updated.teamIds || []),
|
|
252
|
+
updated.workflowId || null,
|
|
253
|
+
updated.taskTypes ? JSON.stringify(updated.taskTypes) : null,
|
|
233
254
|
JSON.stringify(updated.statusDefinitions || []),
|
|
234
255
|
JSON.stringify(updated.priorityDefinitions || []),
|
|
235
256
|
JSON.stringify(updated.customFieldDefinitions || []),
|
|
@@ -239,6 +260,77 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
239
260
|
return updated;
|
|
240
261
|
}
|
|
241
262
|
|
|
263
|
+
// --- Workflows ---
|
|
264
|
+
async getWorkflows(): Promise<Workflow[]> {
|
|
265
|
+
const stmt = this.db.prepare('SELECT * FROM workflows');
|
|
266
|
+
const rows = stmt.all() as any[];
|
|
267
|
+
return rows.map((r) => this.mapWorkflow(r));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async getWorkflow(id: string): Promise<Workflow | null> {
|
|
271
|
+
const stmt = this.db.prepare('SELECT * FROM workflows WHERE id = ?');
|
|
272
|
+
const row = stmt.get(id) as any;
|
|
273
|
+
return row ? this.mapWorkflow(row) : null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async createWorkflow(workflow: Omit<Workflow, 'id' | 'createdAt' | 'updatedAt'>): Promise<Workflow> {
|
|
277
|
+
const id = `wf_${Math.random().toString(36).substring(2, 9)}`;
|
|
278
|
+
const now = new Date().toISOString();
|
|
279
|
+
const newWf: Workflow = { ...workflow, id, createdAt: now, updatedAt: now };
|
|
280
|
+
|
|
281
|
+
const stmt = this.db.prepare(`
|
|
282
|
+
INSERT INTO workflows (id, name, description, statuses, transitions, taskTypes, defaultStatusKey, isDefault, createdAt, updatedAt)
|
|
283
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
284
|
+
`);
|
|
285
|
+
stmt.run(
|
|
286
|
+
newWf.id,
|
|
287
|
+
newWf.name,
|
|
288
|
+
newWf.description || null,
|
|
289
|
+
JSON.stringify(newWf.statuses || []),
|
|
290
|
+
JSON.stringify(newWf.transitions || []),
|
|
291
|
+
newWf.taskTypes ? JSON.stringify(newWf.taskTypes) : null,
|
|
292
|
+
newWf.defaultStatusKey || null,
|
|
293
|
+
newWf.isDefault ? 1 : 0,
|
|
294
|
+
newWf.createdAt,
|
|
295
|
+
newWf.updatedAt
|
|
296
|
+
);
|
|
297
|
+
return newWf;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async updateWorkflow(id: string, updates: Partial<Workflow>): Promise<Workflow | null> {
|
|
301
|
+
const existing = await this.getWorkflow(id);
|
|
302
|
+
if (!existing) return null;
|
|
303
|
+
|
|
304
|
+
const updated: Workflow = {
|
|
305
|
+
...existing,
|
|
306
|
+
...updates,
|
|
307
|
+
updatedAt: new Date().toISOString()
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const stmt = this.db.prepare(`
|
|
311
|
+
UPDATE workflows SET name = ?, description = ?, statuses = ?, transitions = ?, taskTypes = ?, defaultStatusKey = ?, isDefault = ?, updatedAt = ?
|
|
312
|
+
WHERE id = ?
|
|
313
|
+
`);
|
|
314
|
+
stmt.run(
|
|
315
|
+
updated.name,
|
|
316
|
+
updated.description || null,
|
|
317
|
+
JSON.stringify(updated.statuses || []),
|
|
318
|
+
JSON.stringify(updated.transitions || []),
|
|
319
|
+
updated.taskTypes ? JSON.stringify(updated.taskTypes) : null,
|
|
320
|
+
updated.defaultStatusKey || null,
|
|
321
|
+
updated.isDefault ? 1 : 0,
|
|
322
|
+
updated.updatedAt,
|
|
323
|
+
id
|
|
324
|
+
);
|
|
325
|
+
return updated;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async deleteWorkflow(id: string): Promise<boolean> {
|
|
329
|
+
const stmt = this.db.prepare('DELETE FROM workflows WHERE id = ?');
|
|
330
|
+
const result = stmt.run(id);
|
|
331
|
+
return (result.changes ?? 0) > 0;
|
|
332
|
+
}
|
|
333
|
+
|
|
242
334
|
async deleteProject(id: string): Promise<boolean> {
|
|
243
335
|
const stmt = this.db.prepare('DELETE FROM projects WHERE id = ?');
|
|
244
336
|
const result = stmt.run(id);
|
|
@@ -394,12 +486,12 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
394
486
|
|
|
395
487
|
const stmt = this.db.prepare(`
|
|
396
488
|
INSERT INTO tasks (
|
|
397
|
-
id, projectId, title, description, status, priority, assigneeId, reporterId,
|
|
489
|
+
id, projectId, title, description, status, priority, taskType, assigneeId, reporterId,
|
|
398
490
|
reviewerId, iterationId, teamId, containerId, plannedStartDate, actualStartDate, actualEndDate,
|
|
399
491
|
dueDate, estimatedHours, loggedHours, actualHours, billableHours,
|
|
400
492
|
estimatedDurationMinutes, actualDurationMinutes, billableDurationMinutes, progress,
|
|
401
493
|
tags, customFields, parentId, createdAt, updatedAt
|
|
402
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
494
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
403
495
|
`);
|
|
404
496
|
stmt.run(
|
|
405
497
|
newTask.id,
|
|
@@ -408,6 +500,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
408
500
|
newTask.description || null,
|
|
409
501
|
newTask.status,
|
|
410
502
|
newTask.priority,
|
|
503
|
+
newTask.taskType || null,
|
|
411
504
|
newTask.assigneeId || null,
|
|
412
505
|
newTask.reporterId || null,
|
|
413
506
|
newTask.reviewerId || null,
|
|
@@ -447,7 +540,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
447
540
|
|
|
448
541
|
const stmt = this.db.prepare(`
|
|
449
542
|
UPDATE tasks SET
|
|
450
|
-
projectId = ?, title = ?, description = ?, status = ?, priority = ?,
|
|
543
|
+
projectId = ?, title = ?, description = ?, status = ?, priority = ?, taskType = ?,
|
|
451
544
|
assigneeId = ?, reporterId = ?, reviewerId = ?, iterationId = ?, teamId = ?, containerId = ?,
|
|
452
545
|
plannedStartDate = ?, actualStartDate = ?, actualEndDate = ?, dueDate = ?,
|
|
453
546
|
estimatedHours = ?, loggedHours = ?, actualHours = ?, billableHours = ?,
|
|
@@ -461,6 +554,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
461
554
|
updated.description || null,
|
|
462
555
|
updated.status,
|
|
463
556
|
updated.priority,
|
|
557
|
+
updated.taskType || null,
|
|
464
558
|
updated.assigneeId || null,
|
|
465
559
|
updated.reporterId || null,
|
|
466
560
|
updated.reviewerId || null,
|
|
@@ -706,6 +800,8 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
706
800
|
private mapProject(row: any): Project {
|
|
707
801
|
return {
|
|
708
802
|
...row,
|
|
803
|
+
workflowId: row.workflowId || undefined,
|
|
804
|
+
taskTypes: row.taskTypes ? JSON.parse(row.taskTypes) : undefined,
|
|
709
805
|
members: row.members ? JSON.parse(row.members) : [],
|
|
710
806
|
teamIds: row.teamIds ? JSON.parse(row.teamIds) : [],
|
|
711
807
|
statusDefinitions: row.statusDefinitions ? JSON.parse(row.statusDefinitions) : [],
|
|
@@ -714,6 +810,16 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
714
810
|
};
|
|
715
811
|
}
|
|
716
812
|
|
|
813
|
+
private mapWorkflow(row: any): Workflow {
|
|
814
|
+
return {
|
|
815
|
+
...row,
|
|
816
|
+
statuses: row.statuses ? JSON.parse(row.statuses) : [],
|
|
817
|
+
transitions: row.transitions ? JSON.parse(row.transitions) : [],
|
|
818
|
+
taskTypes: row.taskTypes ? JSON.parse(row.taskTypes) : undefined,
|
|
819
|
+
isDefault: row.isDefault !== null && row.isDefault !== undefined ? Boolean(row.isDefault) : undefined
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
717
823
|
private mapTeam(row: any): Team {
|
|
718
824
|
return {
|
|
719
825
|
...row,
|
|
@@ -724,6 +830,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
724
830
|
private mapTask(row: any): Task {
|
|
725
831
|
return {
|
|
726
832
|
...row,
|
|
833
|
+
taskType: row.taskType || undefined,
|
|
727
834
|
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
728
835
|
customFields: row.customFields ? JSON.parse(row.customFields) : {}
|
|
729
836
|
};
|
package/src/types/index.ts
CHANGED
|
@@ -46,6 +46,35 @@ export interface CustomFieldDefinition {
|
|
|
46
46
|
defaultValue?: unknown;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export interface WorkflowTransition {
|
|
50
|
+
id?: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
fromStatusKey: string | '*';
|
|
53
|
+
toStatusKey: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TaskTypeDefinition {
|
|
57
|
+
key: string;
|
|
58
|
+
label: string;
|
|
59
|
+
description?: string;
|
|
60
|
+
icon?: string;
|
|
61
|
+
defaultStatusKey?: string;
|
|
62
|
+
workflowId?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Workflow {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
statuses: StatusDefinition[];
|
|
70
|
+
transitions: WorkflowTransition[];
|
|
71
|
+
taskTypes?: TaskTypeDefinition[];
|
|
72
|
+
defaultStatusKey?: string;
|
|
73
|
+
isDefault?: boolean;
|
|
74
|
+
createdAt: string;
|
|
75
|
+
updatedAt: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
49
78
|
export interface Project {
|
|
50
79
|
id: string;
|
|
51
80
|
key?: string; // e.g. "CP" or "PROJ"
|
|
@@ -54,6 +83,9 @@ export interface Project {
|
|
|
54
83
|
ownerId?: string;
|
|
55
84
|
members?: string[]; // user IDs
|
|
56
85
|
teamIds?: string[]; // team IDs
|
|
86
|
+
workflowId?: string;
|
|
87
|
+
workflow?: Workflow;
|
|
88
|
+
taskTypes?: TaskTypeDefinition[];
|
|
57
89
|
statusDefinitions?: StatusDefinition[];
|
|
58
90
|
priorityDefinitions?: Array<{ key: string; label: string; level?: number }>;
|
|
59
91
|
customFieldDefinitions?: CustomFieldDefinition[];
|
|
@@ -95,6 +127,7 @@ export interface Task {
|
|
|
95
127
|
description?: string;
|
|
96
128
|
status: TaskStatus;
|
|
97
129
|
priority: Priority;
|
|
130
|
+
taskType?: string; // e.g. "bug", "feature", "task", "epic"
|
|
98
131
|
assigneeId?: string;
|
|
99
132
|
reporterId?: string;
|
|
100
133
|
reviewerId?: string;
|
|
@@ -184,7 +217,10 @@ export type WebhookEvent =
|
|
|
184
217
|
| 'iteration.started'
|
|
185
218
|
| 'iteration.completed'
|
|
186
219
|
| 'team.created'
|
|
187
|
-
| 'container.created'
|
|
220
|
+
| 'container.created'
|
|
221
|
+
| 'workflow.created'
|
|
222
|
+
| 'workflow.updated'
|
|
223
|
+
| 'workflow.deleted';
|
|
188
224
|
|
|
189
225
|
export interface PluginHooks {
|
|
190
226
|
beforeTaskCreate?: (task: Partial<Task>) => Promise<Partial<Task>> | Partial<Task>;
|
|
@@ -216,5 +252,6 @@ export interface CriticalPathConfig {
|
|
|
216
252
|
iterations?: Iteration[];
|
|
217
253
|
teams?: Team[];
|
|
218
254
|
containers?: TaskContainer[];
|
|
255
|
+
workflows?: Workflow[];
|
|
219
256
|
};
|
|
220
257
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Workflow,
|
|
3
|
+
StatusDefinition,
|
|
4
|
+
TaskTypeDefinition
|
|
5
|
+
} from '../types/index.js';
|
|
6
|
+
import { DEFAULT_STATUS_DEFINITIONS } from './status.js';
|
|
7
|
+
|
|
8
|
+
export class WorkflowValidationError extends Error {
|
|
9
|
+
public readonly fromStatus: string;
|
|
10
|
+
public readonly toStatus: string;
|
|
11
|
+
public readonly workflowId?: string;
|
|
12
|
+
|
|
13
|
+
constructor(fromStatus: string, toStatus: string, workflowId?: string, message?: string) {
|
|
14
|
+
const msg = message || `Illegal status transition from "${fromStatus}" to "${toStatus}"${workflowId ? ` in workflow "${workflowId}"` : ''}.`;
|
|
15
|
+
super(msg);
|
|
16
|
+
this.name = 'WorkflowValidationError';
|
|
17
|
+
this.fromStatus = fromStatus;
|
|
18
|
+
this.toStatus = toStatus;
|
|
19
|
+
this.workflowId = workflowId;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_TASK_TYPES: TaskTypeDefinition[] = [
|
|
24
|
+
{ key: 'task', label: 'Task', description: 'A standard work item', icon: 'check-square' },
|
|
25
|
+
{ key: 'bug', label: 'Bug', description: 'A problem or defect that needs fixing', icon: 'bug' },
|
|
26
|
+
{ key: 'feature', label: 'Feature', description: 'A new capability or enhancement', icon: 'sparkles' },
|
|
27
|
+
{ key: 'epic', label: 'Epic', description: 'A large body of work that spans multiple tasks', icon: 'layers' },
|
|
28
|
+
{ key: 'subtask', label: 'Subtask', description: 'A piece of work required to complete a parent task', icon: 'corner-down-right' }
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_SOFTWARE_WORKFLOW: Workflow = {
|
|
32
|
+
id: 'wf_software_default',
|
|
33
|
+
name: 'Software Development Workflow',
|
|
34
|
+
description: 'Standard software development lifecycle with backlog, review, and completion states.',
|
|
35
|
+
statuses: [
|
|
36
|
+
DEFAULT_STATUS_DEFINITIONS.backlog,
|
|
37
|
+
DEFAULT_STATUS_DEFINITIONS.todo,
|
|
38
|
+
DEFAULT_STATUS_DEFINITIONS.in_progress,
|
|
39
|
+
DEFAULT_STATUS_DEFINITIONS.in_review,
|
|
40
|
+
DEFAULT_STATUS_DEFINITIONS.done,
|
|
41
|
+
DEFAULT_STATUS_DEFINITIONS.canceled
|
|
42
|
+
],
|
|
43
|
+
transitions: [
|
|
44
|
+
{ name: 'Start Work', fromStatusKey: 'backlog', toStatusKey: 'todo' },
|
|
45
|
+
{ name: 'Start Implementation', fromStatusKey: 'todo', toStatusKey: 'in_progress' },
|
|
46
|
+
{ name: 'Complete Task', fromStatusKey: 'todo', toStatusKey: 'done' },
|
|
47
|
+
{ name: 'Submit for Review', fromStatusKey: 'in_progress', toStatusKey: 'in_review' },
|
|
48
|
+
{ name: 'Complete Implementation', fromStatusKey: 'in_progress', toStatusKey: 'done' },
|
|
49
|
+
{ name: 'Approve & Complete', fromStatusKey: 'in_review', toStatusKey: 'done' },
|
|
50
|
+
{ name: 'Request Changes', fromStatusKey: 'in_review', toStatusKey: 'in_progress' },
|
|
51
|
+
{ name: 'Reopen', fromStatusKey: 'done', toStatusKey: 'todo' },
|
|
52
|
+
{ name: 'Cancel', fromStatusKey: '*', toStatusKey: 'canceled' },
|
|
53
|
+
{ name: 'Restore', fromStatusKey: 'canceled', toStatusKey: 'todo' }
|
|
54
|
+
],
|
|
55
|
+
taskTypes: DEFAULT_TASK_TYPES,
|
|
56
|
+
defaultStatusKey: 'todo',
|
|
57
|
+
isDefault: true,
|
|
58
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
59
|
+
updatedAt: '2026-01-01T00:00:00.000Z'
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const DEFAULT_SIMPLE_WORKFLOW: Workflow = {
|
|
63
|
+
id: 'wf_simple_default',
|
|
64
|
+
name: 'Simple Task Workflow',
|
|
65
|
+
description: 'Lightweight workflow with To Do, In Progress, and Done states.',
|
|
66
|
+
statuses: [
|
|
67
|
+
DEFAULT_STATUS_DEFINITIONS.todo,
|
|
68
|
+
DEFAULT_STATUS_DEFINITIONS.in_progress,
|
|
69
|
+
DEFAULT_STATUS_DEFINITIONS.done
|
|
70
|
+
],
|
|
71
|
+
transitions: [
|
|
72
|
+
{ name: 'Start', fromStatusKey: 'todo', toStatusKey: 'in_progress' },
|
|
73
|
+
{ name: 'Complete', fromStatusKey: 'todo', toStatusKey: 'done' },
|
|
74
|
+
{ name: 'Complete', fromStatusKey: 'in_progress', toStatusKey: 'done' },
|
|
75
|
+
{ name: 'Reopen', fromStatusKey: 'done', toStatusKey: 'todo' }
|
|
76
|
+
],
|
|
77
|
+
taskTypes: DEFAULT_TASK_TYPES.filter((t) => ['task', 'subtask'].includes(t.key)),
|
|
78
|
+
defaultStatusKey: 'todo',
|
|
79
|
+
isDefault: true,
|
|
80
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
81
|
+
updatedAt: '2026-01-01T00:00:00.000Z'
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export function validateTransition(
|
|
85
|
+
workflow: Workflow | undefined,
|
|
86
|
+
fromStatus: string,
|
|
87
|
+
toStatus: string
|
|
88
|
+
): boolean {
|
|
89
|
+
if (fromStatus === toStatus) return true;
|
|
90
|
+
if (!workflow || !workflow.transitions || workflow.transitions.length === 0) return true;
|
|
91
|
+
|
|
92
|
+
return workflow.transitions.some(
|
|
93
|
+
(t) =>
|
|
94
|
+
(t.fromStatusKey === '*' || t.fromStatusKey === fromStatus) &&
|
|
95
|
+
(t.toStatusKey === '*' || t.toStatusKey === toStatus)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getAllowedNextStatuses(
|
|
100
|
+
workflow: Workflow | undefined,
|
|
101
|
+
currentStatus: string
|
|
102
|
+
): string[] {
|
|
103
|
+
if (!workflow || !workflow.statuses || workflow.statuses.length === 0) {
|
|
104
|
+
return Object.keys(DEFAULT_STATUS_DEFINITIONS);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const allStatusKeys = workflow.statuses.map((s) => s.key);
|
|
108
|
+
if (!workflow.transitions || workflow.transitions.length === 0) {
|
|
109
|
+
return allStatusKeys;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const matches = workflow.transitions.filter(
|
|
113
|
+
(t) => t.fromStatusKey === '*' || t.fromStatusKey === currentStatus
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (matches.some((t) => t.toStatusKey === '*')) {
|
|
117
|
+
return allStatusKeys;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const allowed = new Set<string>();
|
|
121
|
+
for (const t of matches) {
|
|
122
|
+
if (allStatusKeys.includes(t.toStatusKey)) {
|
|
123
|
+
allowed.add(t.toStatusKey);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return Array.from(allowed);
|
|
128
|
+
}
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/index.test.ts","./src/index.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/types/index.ts","./src/utils/index.ts","./src/utils/key.test.ts","./src/utils/key.ts","./src/utils/status.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/index.test.ts","./src/index.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/types/index.ts","./src/utils/index.ts","./src/utils/key.test.ts","./src/utils/key.ts","./src/utils/status.ts","./src/utils/workflow.ts"],"version":"5.9.3"}
|