@critical-path/core 0.13.3 → 0.15.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 (58) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +22 -0
  3. package/dist/deliverables.test.js +2 -2
  4. package/dist/deliverables.test.js.map +1 -1
  5. package/dist/domain/entities.d.ts +2 -1
  6. package/dist/domain/entities.d.ts.map +1 -1
  7. package/dist/domain/entities.js +11 -4
  8. package/dist/domain/entities.js.map +1 -1
  9. package/dist/engine/index.d.ts +8 -3
  10. package/dist/engine/index.d.ts.map +1 -1
  11. package/dist/engine/index.js +56 -13
  12. package/dist/engine/index.js.map +1 -1
  13. package/dist/index.test.js +8 -8
  14. package/dist/index.test.js.map +1 -1
  15. package/dist/status.test.d.ts +2 -0
  16. package/dist/status.test.d.ts.map +1 -0
  17. package/dist/status.test.js +199 -0
  18. package/dist/status.test.js.map +1 -0
  19. package/dist/storage/file-storage.d.ts +1 -0
  20. package/dist/storage/file-storage.d.ts.map +1 -1
  21. package/dist/storage/file-storage.js +7 -0
  22. package/dist/storage/file-storage.js.map +1 -1
  23. package/dist/storage/firebase-storage.d.ts +2 -0
  24. package/dist/storage/firebase-storage.d.ts.map +1 -1
  25. package/dist/storage/firebase-storage.js +20 -0
  26. package/dist/storage/firebase-storage.js.map +1 -1
  27. package/dist/storage/storage.test.js +32 -0
  28. package/dist/storage/storage.test.js.map +1 -1
  29. package/dist/store/firebase.test.js +1 -1
  30. package/dist/store/firebase.test.js.map +1 -1
  31. package/dist/store/sqlite.test.js +1 -1
  32. package/dist/store/sqlite.test.js.map +1 -1
  33. package/dist/types/index.d.ts +22 -5
  34. package/dist/types/index.d.ts.map +1 -1
  35. package/dist/use-case-validation.test.js +1 -1
  36. package/dist/use-case-validation.test.js.map +1 -1
  37. package/dist/utils/status.d.ts +7 -10
  38. package/dist/utils/status.d.ts.map +1 -1
  39. package/dist/utils/status.js +104 -21
  40. package/dist/utils/status.js.map +1 -1
  41. package/dist/utils/workflow.js +15 -15
  42. package/dist/utils/workflow.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/deliverables.test.ts +2 -2
  45. package/src/domain/entities.ts +15 -5
  46. package/src/engine/index.ts +71 -16
  47. package/src/index.test.ts +8 -8
  48. package/src/status.test.ts +226 -0
  49. package/src/storage/file-storage.ts +8 -0
  50. package/src/storage/firebase-storage.ts +20 -0
  51. package/src/storage/storage.test.ts +40 -0
  52. package/src/store/firebase.test.ts +1 -1
  53. package/src/store/sqlite.test.ts +1 -1
  54. package/src/types/index.ts +24 -5
  55. package/src/use-case-validation.test.ts +1 -1
  56. package/src/utils/status.ts +119 -33
  57. package/src/utils/workflow.ts +15 -15
  58. package/tsconfig.tsbuildinfo +1 -1
@@ -52,6 +52,11 @@ export class InMemoryFirebaseStorageMock implements FirebaseStorageBucketInterfa
52
52
  async delete() {
53
53
  files.delete(path);
54
54
  },
55
+ async download(): Promise<[Uint8Array]> {
56
+ const file = files.get(path);
57
+ if (!file) throw new Error(`File not found: ${path}`);
58
+ return [file.data];
59
+ },
55
60
  async getMetadata(): Promise<[any]> {
56
61
  const file = files.get(path);
57
62
  return [{
@@ -146,6 +151,21 @@ export class FirebaseStorageAdapter implements FileStorageAdapter {
146
151
  }
147
152
  }
148
153
 
154
+ async download(storageKey: string): Promise<Buffer | Uint8Array> {
155
+ const fileRef = this.bucket.file(storageKey);
156
+ if (typeof (fileRef as any).download === 'function') {
157
+ const [buf] = await (fileRef as any).download();
158
+ return buf;
159
+ }
160
+ const url = await this.getDownloadUrl(storageKey);
161
+ const res = await fetch(url);
162
+ if (!res.ok) {
163
+ throw new Error(`Failed to download file from "${url}": ${res.statusText}`);
164
+ }
165
+ const arrayBuffer = await res.arrayBuffer();
166
+ return new Uint8Array(arrayBuffer);
167
+ }
168
+
149
169
  async getDownloadUrl(storageKey: string): Promise<string> {
150
170
  const fileRef = this.bucket.file(storageKey);
151
171
  if (typeof (fileRef as any).getMetadata === 'function') {
@@ -172,5 +172,45 @@ describe('Storage Adapters', () => {
172
172
  expect(storedUri?.sizeBytes).toBe(Buffer.from(rawText).length);
173
173
  expect(new TextDecoder().decode(storedUri?.data)).toBe(rawText);
174
174
  });
175
+
176
+ it('downloads file bytes and reads text content from attachments via engine', async () => {
177
+ const mockStorage = new InMemoryFirebaseStorageMock();
178
+ const storageAdapter = new FirebaseStorageAdapter({ bucket: mockStorage, bucketName: 'test-bucket' });
179
+ const engine = new CriticalPathEngine({ fileStorage: storageAdapter });
180
+
181
+ const project = await engine.createProject({ name: 'Attachment Reading Test' });
182
+ const task = await engine.createTask({ projectId: project.id, title: 'Document Task' });
183
+
184
+ const planMarkdown = '# Project Roadmap\n\nPhase 1: Research\nPhase 2: Execution';
185
+ const attachment = await engine.uploadAttachmentFile({
186
+ filename: 'Roadmap.md',
187
+ data: planMarkdown,
188
+ mimeType: 'text/markdown',
189
+ taskId: task.id,
190
+ projectId: project.id,
191
+ uploaderId: 'agent_planner',
192
+ uploaderType: 'agent',
193
+ artifactType: 'plan'
194
+ });
195
+
196
+ expect(attachment.id).toBeDefined();
197
+ expect(attachment.artifactType).toBe('plan');
198
+
199
+ // Test download method on adapter
200
+ const downloadedBytes = await storageAdapter.download(attachment.storageKey!);
201
+ expect(new TextDecoder().decode(downloadedBytes)).toBe(planMarkdown);
202
+
203
+ // Test engine readAttachmentText
204
+ const readText = await engine.readAttachmentText(attachment.id);
205
+ expect(readText).toBe(planMarkdown);
206
+
207
+ // Test filtering by artifactType
208
+ const planAttachments = await engine.getAttachments({ taskId: task.id, artifactType: 'plan' });
209
+ expect(planAttachments.length).toBe(1);
210
+ expect(planAttachments[0].id).toBe(attachment.id);
211
+
212
+ const reviewAttachments = await engine.getAttachments({ taskId: task.id, artifactType: 'review' });
213
+ expect(reviewAttachments.length).toBe(0);
214
+ });
175
215
  });
176
216
  });
@@ -62,7 +62,7 @@ describe('FirebaseStore', () => {
62
62
  name: 'Firebase Workflow',
63
63
  isDefault: false,
64
64
  defaultStatusKey: 'backlog',
65
- statuses: [{ key: 'backlog', label: 'Backlog', completionState: 'not_done', executionState: 'inactive' }],
65
+ statuses: [{ key: 'backlog', label: 'Backlog', category: 'not_started' }],
66
66
  transitions: []
67
67
  });
68
68
 
@@ -155,7 +155,7 @@ describe('SQLiteStore', () => {
155
155
  name: 'SQLite Workflow',
156
156
  isDefault: true,
157
157
  defaultStatusKey: 'todo',
158
- statuses: [{ key: 'todo', label: 'To Do', completionState: 'not_done', executionState: 'inactive' }],
158
+ statuses: [{ key: 'todo', label: 'To Do', category: 'not_started' }],
159
159
  transitions: []
160
160
  });
161
161
 
@@ -4,17 +4,33 @@ export type Priority = DefaultPriority | (string & {});
4
4
  export type DefaultTaskStatus = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'canceled';
5
5
  export type TaskStatus = DefaultTaskStatus | (string & {});
6
6
 
7
- export type CompletionState = 'done' | 'not_done';
8
- export type ExecutionState = 'active' | 'inactive';
7
+ export type SemanticStatus = 'not_started' | 'in_progress' | 'completed' | 'canceled';
9
8
 
10
9
  export interface StatusDefinition {
11
10
  key: string;
12
11
  label: string;
13
- completionState: CompletionState;
14
- executionState: ExecutionState;
15
- isCancelled?: boolean;
12
+ category: SemanticStatus;
16
13
  }
17
14
 
15
+ export interface TaskDerivedStatus {
16
+ semanticStatus: SemanticStatus;
17
+ isReady: boolean;
18
+ isBlocked: boolean;
19
+ blockingTaskIds: string[];
20
+ isOverdue: boolean;
21
+ isUpcoming: boolean;
22
+ isUnplanned: boolean;
23
+ isUnassigned: boolean;
24
+ isStalled: boolean;
25
+ isOverEstimate: boolean;
26
+ isPaceWarning: boolean;
27
+ isDone: boolean;
28
+ isActive: boolean;
29
+ isCancelled: boolean;
30
+ }
31
+
32
+ export type TaskLifecycleState = TaskDerivedStatus;
33
+
18
34
  export type Role = 'admin' | 'project_manager' | 'contributor' | 'viewer';
19
35
 
20
36
  export interface User {
@@ -142,6 +158,7 @@ export interface Task {
142
158
  title: string;
143
159
  description?: string;
144
160
  status: TaskStatus;
161
+ semanticStatus?: SemanticStatus;
145
162
  priority: Priority;
146
163
  taskType?: string; // e.g. "bug", "feature", "task", "epic"
147
164
  assigneeId?: string;
@@ -271,6 +288,7 @@ export interface Attachment {
271
288
  sizeBytes: number;
272
289
  url: string;
273
290
  storageKey?: string;
291
+ artifactType?: 'plan' | 'spec' | 'deliverable' | 'review' | 'general';
274
292
  metadata?: Record<string, unknown>;
275
293
  createdAt: string;
276
294
  updatedAt: string;
@@ -309,6 +327,7 @@ export interface PresignedUploadResult {
309
327
  export interface FileStorageAdapter {
310
328
  upload(input: UploadFileInput): Promise<UploadFileResult>;
311
329
  delete(storageKey: string): Promise<boolean>;
330
+ download?(storageKey: string): Promise<Buffer | Uint8Array>;
312
331
  getDownloadUrl?(storageKey: string): Promise<string>;
313
332
  getPresignedUploadUrl?(options: PresignedUrlOptions): Promise<PresignedUploadResult>;
314
333
  }
@@ -138,7 +138,7 @@ describe('Critical Path Use Case Validation Suite', () => {
138
138
  // Check Lifecycle State
139
139
  const lifecycle = await engine.getTaskLifecycleState(featureTask.id);
140
140
  expect(lifecycle?.isDone).toBe(true);
141
- expect(lifecycle?.completionState).toBe('done');
141
+ expect(lifecycle?.semanticStatus).toBe('completed');
142
142
  });
143
143
  });
144
144
 
@@ -1,27 +1,24 @@
1
1
  import type {
2
2
  Task,
3
3
  StatusDefinition,
4
- CompletionState,
5
- ExecutionState
4
+ SemanticStatus,
5
+ TaskDerivedStatus,
6
6
  } from '../types/index.js';
7
7
 
8
8
  export const DEFAULT_STATUS_DEFINITIONS: Record<string, StatusDefinition> = {
9
- backlog: { key: 'backlog', label: 'Backlog', completionState: 'not_done', executionState: 'inactive' },
10
- todo: { key: 'todo', label: 'To Do', completionState: 'not_done', executionState: 'inactive' },
11
- in_progress: { key: 'in_progress', label: 'In Progress', completionState: 'not_done', executionState: 'active' },
12
- in_review: { key: 'in_review', label: 'In Review', completionState: 'not_done', executionState: 'active' },
13
- done: { key: 'done', label: 'Done', completionState: 'done', executionState: 'inactive' },
14
- canceled: { key: 'canceled', label: 'Canceled', completionState: 'done', executionState: 'inactive', isCancelled: true }
9
+ backlog: { key: 'backlog', label: 'Backlog', category: 'not_started' },
10
+ todo: { key: 'todo', label: 'To Do', category: 'not_started' },
11
+ in_progress: { key: 'in_progress', label: 'In Progress', category: 'in_progress' },
12
+ in_review: { key: 'in_review', label: 'In Review', category: 'in_progress' },
13
+ done: { key: 'done', label: 'Done', category: 'completed' },
14
+ canceled: { key: 'canceled', label: 'Canceled', category: 'canceled' }
15
15
  };
16
16
 
17
- export interface TaskLifecycleState {
18
- completionState: CompletionState;
19
- executionState: ExecutionState;
20
- isDone: boolean;
21
- isActive: boolean;
22
- isCancelled: boolean;
23
- isOverdue: boolean;
24
- isUpcoming: boolean;
17
+ export interface DeriveTaskLifecycleOptions {
18
+ customDefinitions?: StatusDefinition[];
19
+ referenceDate?: Date;
20
+ upstreamTasks?: Task[];
21
+ stalledThresholdDays?: number;
25
22
  }
26
23
 
27
24
  export function resolveStatusDefinition(
@@ -35,28 +32,66 @@ export function resolveStatusDefinition(
35
32
  if (DEFAULT_STATUS_DEFINITIONS[statusKey]) {
36
33
  return DEFAULT_STATUS_DEFINITIONS[statusKey];
37
34
  }
35
+
38
36
  // Fallback heuristic for unknown custom statuses:
39
- const isDoneKey = ['done', 'completed', 'finished', 'closed', 'canceled', 'cancelled', 'approved', 'final'].includes(statusKey.toLowerCase());
40
- const isActiveKey = ['in_progress', 'active', 'working', 'in_review', 'review'].includes(statusKey.toLowerCase());
37
+ const lower = statusKey.toLowerCase();
38
+ let category: SemanticStatus = 'not_started';
39
+ if (['canceled', 'cancelled', 'rejected', 'abandoned', 'void', 'dropped'].some((k) => lower.includes(k))) {
40
+ category = 'canceled';
41
+ } else if (['done', 'completed', 'finished', 'closed', 'approved', 'final', 'delivered', 'shipped'].some((k) => lower.includes(k))) {
42
+ category = 'completed';
43
+ } else if (['in_progress', 'active', 'working', 'in_review', 'review', 'testing', 'qa', 'building', 'editing'].some((k) => lower.includes(k))) {
44
+ category = 'in_progress';
45
+ }
46
+
41
47
  return {
42
48
  key: statusKey,
43
49
  label: statusKey,
44
- completionState: isDoneKey ? 'done' : 'not_done',
45
- executionState: isActiveKey ? 'active' : 'inactive',
46
- isCancelled: statusKey.toLowerCase().includes('cancel')
50
+ category
47
51
  };
48
52
  }
49
53
 
50
54
  export function deriveTaskLifecycleState(
51
55
  task: Task,
52
- customDefinitions?: StatusDefinition[],
53
- referenceDate: Date = new Date()
54
- ): TaskLifecycleState {
56
+ customDefinitionsOrOptions?: StatusDefinition[] | DeriveTaskLifecycleOptions,
57
+ legacyReferenceDate?: Date
58
+ ): TaskDerivedStatus {
59
+ let customDefinitions: StatusDefinition[] | undefined;
60
+ let referenceDate = legacyReferenceDate ?? new Date();
61
+ let upstreamTasks: Task[] = [];
62
+ let stalledThresholdDays = 5;
63
+
64
+ if (Array.isArray(customDefinitionsOrOptions)) {
65
+ customDefinitions = customDefinitionsOrOptions;
66
+ } else if (customDefinitionsOrOptions) {
67
+ customDefinitions = customDefinitionsOrOptions.customDefinitions;
68
+ if (customDefinitionsOrOptions.referenceDate) {
69
+ referenceDate = customDefinitionsOrOptions.referenceDate;
70
+ }
71
+ if (customDefinitionsOrOptions.upstreamTasks) {
72
+ upstreamTasks = customDefinitionsOrOptions.upstreamTasks;
73
+ }
74
+ if (customDefinitionsOrOptions.stalledThresholdDays !== undefined) {
75
+ stalledThresholdDays = customDefinitionsOrOptions.stalledThresholdDays;
76
+ }
77
+ }
78
+
55
79
  const def = resolveStatusDefinition(task.status, customDefinitions);
56
- const isDone = def.completionState === 'done';
57
- const isActive = def.executionState === 'active';
58
- const isCancelled = Boolean(def.isCancelled);
80
+ const semanticStatus: SemanticStatus = def.category;
81
+ const isDone = semanticStatus === 'completed' || semanticStatus === 'canceled';
82
+ const isActive = semanticStatus === 'in_progress';
83
+ const isCancelled = semanticStatus === 'canceled';
59
84
 
85
+ // Dependency & Flow Indicators
86
+ const blockingTasks = upstreamTasks.filter((t) => {
87
+ const upstreamDef = resolveStatusDefinition(t.status, customDefinitions);
88
+ return upstreamDef.category !== 'completed';
89
+ });
90
+ const blockingTaskIds = blockingTasks.map((t) => t.id);
91
+ const isBlocked = !isDone && blockingTaskIds.length > 0;
92
+ const isReady = semanticStatus === 'not_started' && blockingTaskIds.length === 0;
93
+
94
+ // Time & Schedule Indicators
60
95
  let isOverdue = false;
61
96
  if (!isDone && task.dueDate) {
62
97
  const due = new Date(task.dueDate);
@@ -66,20 +101,71 @@ export function deriveTaskLifecycleState(
66
101
  }
67
102
 
68
103
  let isUpcoming = false;
69
- if (!isDone && !isActive && task.plannedStartDate) {
104
+ if (semanticStatus === 'not_started' && task.plannedStartDate) {
70
105
  const plannedStart = new Date(task.plannedStartDate);
71
106
  if (!isNaN(plannedStart.getTime()) && plannedStart > referenceDate) {
72
107
  isUpcoming = true;
73
108
  }
74
109
  }
75
110
 
111
+ const isUnplanned = semanticStatus === 'not_started' && !task.iterationId && !task.plannedStartDate && !task.dueDate;
112
+
113
+ // Resource & Activity Indicators
114
+ const hasAssignee = Boolean(task.assigneeId || (task.assignees && task.assignees.length > 0));
115
+ const isUnassigned = !isDone && !hasAssignee;
116
+
117
+ let isStalled = false;
118
+ if (semanticStatus === 'in_progress') {
119
+ const lastActivity = task.updatedAt ? new Date(task.updatedAt) : (task.createdAt ? new Date(task.createdAt) : null);
120
+ if (lastActivity && !isNaN(lastActivity.getTime())) {
121
+ const thresholdMs = stalledThresholdDays * 24 * 60 * 60 * 1000;
122
+ if (referenceDate.getTime() - lastActivity.getTime() > thresholdMs) {
123
+ isStalled = true;
124
+ }
125
+ }
126
+ }
127
+
128
+ // Effort & Estimate Indicators
129
+ let isOverEstimate = false;
130
+ const loggedH = task.loggedHours ?? task.actualHours;
131
+ const estimatedH = task.estimatedHours;
132
+ if (estimatedH !== undefined && estimatedH > 0 && loggedH !== undefined && loggedH > estimatedH) {
133
+ isOverEstimate = true;
134
+ }
135
+ const actualM = task.actualDurationMinutes;
136
+ const estimatedM = task.estimatedDurationMinutes;
137
+ if (estimatedM !== undefined && estimatedM > 0 && actualM !== undefined && actualM > estimatedM) {
138
+ isOverEstimate = true;
139
+ }
140
+
141
+ let isPaceWarning = false;
142
+ if (semanticStatus === 'in_progress' && task.actualStartDate) {
143
+ const start = new Date(task.actualStartDate);
144
+ if (!isNaN(start.getTime())) {
145
+ const elapsedMs = referenceDate.getTime() - start.getTime();
146
+ const elapsedHours = elapsedMs / (1000 * 60 * 60);
147
+ if (estimatedH !== undefined && estimatedH > 0 && elapsedHours > estimatedH) {
148
+ isPaceWarning = true;
149
+ } else if (estimatedM !== undefined && estimatedM > 0 && (elapsedMs / (1000 * 60)) > estimatedM) {
150
+ isPaceWarning = true;
151
+ }
152
+ }
153
+ }
154
+
76
155
  return {
77
- completionState: def.completionState,
78
- executionState: def.executionState,
156
+ semanticStatus,
157
+ isReady,
158
+ isBlocked,
159
+ blockingTaskIds,
160
+ isOverdue,
161
+ isUpcoming,
162
+ isUnplanned,
163
+ isUnassigned,
164
+ isStalled,
165
+ isOverEstimate,
166
+ isPaceWarning,
79
167
  isDone,
80
168
  isActive,
81
- isCancelled,
82
- isOverdue,
83
- isUpcoming
169
+ isCancelled
84
170
  };
85
171
  }
@@ -90,13 +90,13 @@ export const DEFAULT_VFX_WORKFLOW: Workflow = {
90
90
  name: 'VFX Production Workflow',
91
91
  description: 'Visual effects production pipeline from bidding and drafting to internal lead review and client supervisor approval.',
92
92
  statuses: [
93
- { key: 'bidding', label: 'Bidding & Draft', completionState: 'not_done', executionState: 'inactive' },
94
- { key: 'awarded', label: 'Awarded / Ready', completionState: 'not_done', executionState: 'inactive' },
95
- { key: 'in_production', label: 'In Production', completionState: 'not_done', executionState: 'active' },
96
- { key: 'internal_review', label: 'Internal Review', completionState: 'not_done', executionState: 'active' },
97
- { key: 'client_review', label: 'Client Review', completionState: 'not_done', executionState: 'active' },
98
- { key: 'revision_requested', label: 'Revision Requested', completionState: 'not_done', executionState: 'active' },
99
- { key: 'approved', label: 'Approved (Final)', completionState: 'done', executionState: 'inactive' },
93
+ { key: 'bidding', label: 'Bidding & Draft', category: 'not_started' },
94
+ { key: 'awarded', label: 'Awarded / Ready', category: 'not_started' },
95
+ { key: 'in_production', label: 'In Production', category: 'in_progress' },
96
+ { key: 'internal_review', label: 'Internal Review', category: 'in_progress' },
97
+ { key: 'client_review', label: 'Client Review', category: 'in_progress' },
98
+ { key: 'revision_requested', label: 'Revision Requested', category: 'in_progress' },
99
+ { key: 'approved', label: 'Approved (Final)', category: 'completed' },
100
100
  DEFAULT_STATUS_DEFINITIONS.canceled
101
101
  ],
102
102
  transitions: [
@@ -126,14 +126,14 @@ export const DEFAULT_CREATIVE_WORKFLOW: Workflow = {
126
126
  name: 'Creative Production Workflow',
127
127
  description: 'Production pipeline for creative deliverables from briefing and concepts to internal review, client feedback, and final delivery.',
128
128
  statuses: [
129
- { key: 'briefing', label: 'Briefing & Scoping', completionState: 'not_done', executionState: 'inactive' },
130
- { key: 'concept', label: 'Concept & Storyboarding', completionState: 'not_done', executionState: 'active' },
131
- { key: 'in_production', label: 'In Production', completionState: 'not_done', executionState: 'active' },
132
- { key: 'internal_review', label: 'Internal Review', completionState: 'not_done', executionState: 'active' },
133
- { key: 'client_review', label: 'Client Review', completionState: 'not_done', executionState: 'active' },
134
- { key: 'revision_requested', label: 'Revision Requested', completionState: 'not_done', executionState: 'active' },
135
- { key: 'approved', label: 'Approved', completionState: 'done', executionState: 'inactive' },
136
- { key: 'delivered', label: 'Delivered', completionState: 'done', executionState: 'inactive' },
129
+ { key: 'briefing', label: 'Briefing & Scoping', category: 'not_started' },
130
+ { key: 'concept', label: 'Concept & Storyboarding', category: 'in_progress' },
131
+ { key: 'in_production', label: 'In Production', category: 'in_progress' },
132
+ { key: 'internal_review', label: 'Internal Review', category: 'in_progress' },
133
+ { key: 'client_review', label: 'Client Review', category: 'in_progress' },
134
+ { key: 'revision_requested', label: 'Revision Requested', category: 'in_progress' },
135
+ { key: 'approved', label: 'Approved', category: 'completed' },
136
+ { key: 'delivered', label: 'Delivered', category: 'completed' },
137
137
  DEFAULT_STATUS_DEFINITIONS.canceled
138
138
  ],
139
139
  transitions: [
@@ -1 +1 @@
1
- {"root":["./src/deliverables.test.ts","./src/index.test.ts","./src/index.ts","./src/use-case-validation.test.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/engine/index.ts","./src/plugins/index.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/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/mentions.test.ts","./src/utils/mentions.ts","./src/utils/status.ts","./src/utils/workflow.ts"],"version":"5.9.3"}
1
+ {"root":["./src/deliverables.test.ts","./src/index.test.ts","./src/index.ts","./src/status.test.ts","./src/use-case-validation.test.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/engine/index.ts","./src/plugins/index.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/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/mentions.test.ts","./src/utils/mentions.ts","./src/utils/status.ts","./src/utils/workflow.ts"],"version":"5.9.3"}