@taskforcehq/taskforce 0.3.301 → 0.3.302

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 (147) hide show
  1. package/dist/Taskforce.module.css +26 -2
  2. package/dist/TaskforceCore.filter-persistence.test.js +1 -1
  3. package/dist/TaskforceCore.js +13 -24
  4. package/dist/TaskforceCore.test.js +167 -152
  5. package/dist/components/features/TaskSettings.js +14 -82
  6. package/dist/components/features/TaskSettings.test.js +33 -70
  7. package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
  8. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
  9. package/dist/components/task/TaskActionHeader.js +6 -3
  10. package/dist/components/task/TaskCard.js +6 -3
  11. package/dist/components/task/TaskForm.js +2 -2
  12. package/dist/components/task/TaskForm.test.js +41 -2
  13. package/dist/components/ui/ReferenceBadgeButton.js +1 -1
  14. package/dist/components/views/PlansPage.js +9 -8
  15. package/dist/components/views/StandaloneLayout.js +466 -204
  16. package/dist/components/views/panels/PlanningDrawer.js +18 -1
  17. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +43 -0
  18. package/dist/components/views/standalone/modals/AccountHubModal.js +22 -0
  19. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
  20. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
  21. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
  22. package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
  23. package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
  24. package/dist/components/views/standalone/modals/HelpModal.js +8 -0
  25. package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
  26. package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
  27. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
  28. package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
  29. package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
  30. package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
  31. package/dist/components/views/standalone/modals/types.d.ts +2 -0
  32. package/dist/components/views/standalone/modals/types.js +1 -0
  33. package/dist/components/views/teamManagementAccess.d.ts +1 -1
  34. package/dist/components/views/teamManagementAccess.js +2 -2
  35. package/dist/components/views/teamManagementAccess.test.js +3 -3
  36. package/dist/config/deployment.js +2 -2
  37. package/dist/config/deployment.test.js +5 -3
  38. package/dist/core/AiProfileService.js +7 -2
  39. package/dist/core/AiProfiles.test.js +19 -0
  40. package/dist/core/SyncReconciliationService.d.ts +2 -0
  41. package/dist/core/SyncReconciliationService.js +3 -0
  42. package/dist/core/TaskAuditTimeline.test.js +40 -0
  43. package/dist/core/Taskforce.d.ts +49 -0
  44. package/dist/core/Taskforce.ids.test.js +133 -0
  45. package/dist/core/Taskforce.js +241 -78
  46. package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
  47. package/dist/core/UserProfileAvatarDrafts.test.js +102 -0
  48. package/dist/core/taskReferencePolicy.d.ts +28 -0
  49. package/dist/core/taskReferencePolicy.js +189 -0
  50. package/dist/core/types.d.ts +2 -0
  51. package/dist/hooks/tasks/useTaskFormActions.js +3 -0
  52. package/dist/hooks/useTaskData.js +3 -2
  53. package/dist/hooks/useTaskData.test.js +66 -0
  54. package/dist/hooks/useTaskforce.d.ts +5 -0
  55. package/dist/hooks/useTaskforce.js +63 -15
  56. package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
  57. package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
  58. package/dist/mcp/aiProfileBootstrap.js +80 -0
  59. package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
  60. package/dist/mcp/aiProfileBootstrap.test.js +68 -0
  61. package/dist/mcp/clientIntegrations.d.ts +2 -8
  62. package/dist/mcp/clientIntegrations.js +21 -40
  63. package/dist/mcp/clientIntegrations.test.js +28 -2
  64. package/dist/mcp/clientProfileDefaults.d.ts +12 -0
  65. package/dist/mcp/clientProfileDefaults.js +37 -0
  66. package/dist/mcp/runtime.d.ts +10 -7
  67. package/dist/mcp/runtime.js +41 -19
  68. package/dist/mcp/runtime.test.js +49 -2
  69. package/dist/migrations/taskSchemaMigrations.js +53 -1
  70. package/dist/qaOpen.test.d.ts +1 -0
  71. package/dist/qaOpen.test.js +56 -0
  72. package/dist/server/index.cookieProxy.test.js +1 -0
  73. package/dist/server/index.d.ts +1 -0
  74. package/dist/server/index.js +15 -1
  75. package/dist/server/index.test.js +14 -1
  76. package/dist/server/routes/admin.js +25 -22
  77. package/dist/server/routes/auth.d.ts +5 -0
  78. package/dist/server/routes/auth.js +350 -16
  79. package/dist/server/routes/billing.js +11 -1
  80. package/dist/server/routes/billing.test.js +32 -2
  81. package/dist/server/routes/documents.js +6 -1
  82. package/dist/server/routes/shared.d.ts +1 -1
  83. package/dist/server/routes/shared.js +11 -3
  84. package/dist/server/routes/sync.integration.test.js +130 -0
  85. package/dist/server/routes/sync.js +226 -1924
  86. package/dist/server/routes/tasks.js +18 -4
  87. package/dist/server/routes.js +26 -19
  88. package/dist/server/routes.test.js +352 -6
  89. package/dist/shared/runtimeContract.d.ts +1 -1
  90. package/dist/sync/collaborationSyncPayload.d.ts +17 -0
  91. package/dist/sync/collaborationSyncPayload.js +131 -0
  92. package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
  93. package/dist/sync/collaborationSyncPayload.test.js +104 -0
  94. package/dist/sync/collaborationSyncState.d.ts +15 -0
  95. package/dist/sync/collaborationSyncState.js +60 -0
  96. package/dist/sync/contentSyncPayload.d.ts +45 -0
  97. package/dist/sync/contentSyncPayload.js +104 -0
  98. package/dist/sync/contentSyncPayload.test.d.ts +1 -0
  99. package/dist/sync/contentSyncPayload.test.js +104 -0
  100. package/dist/sync/contentSyncState.d.ts +88 -0
  101. package/dist/sync/contentSyncState.js +743 -0
  102. package/dist/sync/planningSyncPayload.d.ts +9 -0
  103. package/dist/sync/planningSyncPayload.js +82 -0
  104. package/dist/sync/planningSyncPayload.test.d.ts +1 -0
  105. package/dist/sync/planningSyncPayload.test.js +68 -0
  106. package/dist/sync/syncApplyHandlers.d.ts +83 -0
  107. package/dist/sync/syncApplyHandlers.js +445 -0
  108. package/dist/sync/taskSyncPayload.d.ts +33 -0
  109. package/dist/sync/taskSyncPayload.js +126 -0
  110. package/dist/sync/taskSyncPayload.test.d.ts +1 -0
  111. package/dist/sync/taskSyncPayload.test.js +54 -0
  112. package/dist/sync/workspacePullFeed.d.ts +28 -0
  113. package/dist/sync/workspacePullFeed.js +301 -0
  114. package/dist/sync/workspacePullFeed.test.d.ts +1 -0
  115. package/dist/sync/workspacePullFeed.test.js +166 -0
  116. package/dist/sync/workspaceSyncModel.d.ts +4 -1
  117. package/dist/sync/workspaceSyncModel.js +9 -2
  118. package/dist/sync/workspaceSyncModel.test.js +29 -0
  119. package/dist/test/setup.js +1 -1
  120. package/dist/types.d.ts +1 -0
  121. package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-JG5jc3he.js} +1 -1
  122. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-Dvi02Y7q.js} +1 -1
  123. package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-iys3zfca.js} +2 -2
  124. package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-DJvLJI3A.js} +1 -1
  125. package/dist/ui/assets/PlansPage-BMPlpFpw.js +1 -0
  126. package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +8 -0
  127. package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
  128. package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-BguHeQaV.js} +1 -1
  129. package/dist/ui/assets/index-MXeWoebC.css +1 -0
  130. package/dist/ui/assets/index-YJ4k5QNa.js +6 -0
  131. package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-C8EED3Oh.js} +1 -1
  132. package/dist/ui/index.html +3 -3
  133. package/dist/utils/billingStatusCache.d.ts +21 -0
  134. package/dist/utils/billingStatusCache.js +70 -0
  135. package/dist/utils/taskActivity.js +17 -4
  136. package/dist/utils/taskActivity.test.js +27 -0
  137. package/dist/utils/taskNormalization.js +10 -2
  138. package/dist/utils/taskReferences.d.ts +11 -1
  139. package/dist/utils/taskReferences.js +33 -1
  140. package/package.json +2 -1
  141. package/scripts/qa-open-lib.mjs +175 -0
  142. package/scripts/qa-open-lib.mjs.d.ts +49 -0
  143. package/scripts/qa-open.mjs +352 -0
  144. package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
  145. package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
  146. package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
  147. package/dist/ui/assets/index-xBWLPOEb.js +0 -6
@@ -31,7 +31,8 @@ import { normalizePriorityDefinitions as normalizePriorityDefinitionsInternal, n
31
31
  import { DEFAULT_CATEGORY_LABEL, DEFAULT_CATEGORY_VALUE, DEFAULT_TYPE_VALUE, } from '../shared/taxonomyDefaults.js';
32
32
  import { cloneDefaultWorkflowDefinitions, DEFAULT_WORKFLOW_VALUE, } from '../shared/workflowDefaults.js';
33
33
  import { getBuiltInTaxonomyLibraryPack, getDefaultStarterTaxonomyLibraryPack, getEmergencyStarterTaxonomyLibraryPack } from '../shared/taxonomyLibrary.js';
34
- import { formatTaskReference, parseTaskReference, } from '../utils/taskReferences.js';
34
+ import { parseTaskReference, } from '../utils/taskReferences.js';
35
+ import { allocateNextLocalTaskReferenceNumber as allocateNextLocalTaskReferenceNumberPolicy, deriveTaskReferenceLabel, allocateNextTaskReferenceNumber as allocateNextTaskReferenceNumberPolicy, getLocalTaskReferenceSequenceValue as getLocalTaskReferenceSequenceValuePolicy, getMaxLocalTaskReferenceNumber as getMaxLocalTaskReferenceNumberPolicy, getMaxTaskReferenceNumber as getMaxTaskReferenceNumberPolicy, getTaskByLocalReferenceNumber as getTaskByLocalReferenceNumberPolicy, getTaskByReferenceNumber as getTaskByReferenceNumberPolicy, getTaskReferenceSequenceValue as getTaskReferenceSequenceValuePolicy, persistLocalTaskReferenceSequence as persistLocalTaskReferenceSequencePolicy, persistTaskReferenceSequence as persistTaskReferenceSequencePolicy, relocateTaskReferenceConflict as relocateTaskReferenceConflictPolicy, reserveTaskReferenceNumber as reserveTaskReferenceNumberPolicy, resolveTaskIdentifier as resolveTaskIdentifierPolicy, } from './taskReferencePolicy.js';
35
36
  import { formatInitiativeReference, formatWorkstreamReference, } from '../utils/planningReferences.js';
36
37
  import { getDocumentReferenceLabel } from '../utils/documentReferences.js';
37
38
  import { getImageReferenceLabel } from '../utils/imageReferences.js';
@@ -1109,7 +1110,7 @@ export class TaskforceCore {
1109
1110
  : path.resolve(this.projectRoot, storagePath);
1110
1111
  this.dbPath = path.resolve(this.basePath, 'taskforce.db');
1111
1112
  this.configFile = path.resolve(this.basePath, 'config.json');
1112
- this.globalSettingsDbPath = path.resolve(os.homedir(), '.tasknexus', 'taskforce-global.db');
1113
+ this.globalSettingsDbPath = path.resolve(os.homedir(), '.taskforce', 'taskforce-global.db');
1113
1114
  this.ensureDirectories();
1114
1115
  this.migrateLegacyGlobalSettingsDbIfNeeded();
1115
1116
  // Initialize Database Adapter (SQLite default; Postgres scaffolded)
@@ -1269,7 +1270,6 @@ export class TaskforceCore {
1269
1270
  if (fs.existsSync(this.globalSettingsDbPath))
1270
1271
  return;
1271
1272
  const candidates = [
1272
- path.resolve(os.homedir(), '.taskforce', 'taskforce-global.db'),
1273
1273
  path.resolve(this.basePath, '_global', 'taskforce-global.db')
1274
1274
  ];
1275
1275
  for (const legacyPath of candidates) {
@@ -1297,6 +1297,7 @@ export class TaskforceCore {
1297
1297
  tenant_id TEXT NOT NULL DEFAULT 'default',
1298
1298
  workspace_id TEXT NOT NULL DEFAULT 'default',
1299
1299
  reference_number INTEGER,
1300
+ local_reference_number INTEGER,
1300
1301
  title TEXT NOT NULL,
1301
1302
  description TEXT,
1302
1303
  status TEXT NOT NULL,
@@ -1331,6 +1332,13 @@ export class TaskforceCore {
1331
1332
  PRIMARY KEY (tenant_id, workspace_id)
1332
1333
  );
1333
1334
 
1335
+ CREATE TABLE IF NOT EXISTS workspace_local_task_reference_sequences (
1336
+ tenant_id TEXT NOT NULL DEFAULT 'default',
1337
+ workspace_id TEXT NOT NULL DEFAULT 'default',
1338
+ next_reference_number INTEGER NOT NULL DEFAULT 0,
1339
+ PRIMARY KEY (tenant_id, workspace_id)
1340
+ );
1341
+
1334
1342
  CREATE TABLE IF NOT EXISTS initiatives (
1335
1343
  id TEXT PRIMARY KEY,
1336
1344
  tenant_id TEXT NOT NULL DEFAULT 'default',
@@ -2788,10 +2796,12 @@ export class TaskforceCore {
2788
2796
  const dueDate = this.sanitizeDateOnly(row.due_date);
2789
2797
  const scheduledWeekKey = row.scheduled_week_key || (scheduledDate ? this.dateOnlyToIsoWeekKey(scheduledDate) : null);
2790
2798
  const referenceNumber = this.sanitizeTaskReferenceNumber(row.reference_number);
2799
+ const localReferenceNumber = this.sanitizeTaskReferenceNumber(row.local_reference_number);
2791
2800
  return {
2792
2801
  id: taskId,
2793
2802
  referenceNumber,
2794
- referenceLabel: formatTaskReference(referenceNumber),
2803
+ localReferenceNumber,
2804
+ referenceLabel: deriveTaskReferenceLabel({ referenceNumber, localReferenceNumber }),
2795
2805
  title: row.title,
2796
2806
  description: row.description,
2797
2807
  status: this.normalizeStatusValue(row.status),
@@ -2879,31 +2889,24 @@ export class TaskforceCore {
2879
2889
  const normalized = Math.floor(numeric);
2880
2890
  return normalized > 0 ? normalized : null;
2881
2891
  }
2892
+ getTaskReferencePolicyDeps() {
2893
+ return {
2894
+ db: this.db,
2895
+ tenantId: this.tenantId,
2896
+ normalizeWorkspaceId: this.normalizeWorkspaceId.bind(this),
2897
+ sanitizeTaskReferenceNumber: this.sanitizeTaskReferenceNumber.bind(this),
2898
+ rowToTask: this.rowToTask.bind(this),
2899
+ getTask: this.getTask.bind(this),
2900
+ };
2901
+ }
2882
2902
  getTaskByReferenceNumber(referenceNumber, workspaceId) {
2883
- const normalizedReferenceNumber = this.sanitizeTaskReferenceNumber(referenceNumber);
2884
- if (!normalizedReferenceNumber)
2885
- return null;
2886
- const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
2887
- const row = this.db.prepare(`
2888
- SELECT * FROM tasks
2889
- WHERE tenant_id = ? AND workspace_id = ? AND reference_number = ?
2890
- LIMIT 1
2891
- `).get(this.tenantId, normalizedWorkspaceId, normalizedReferenceNumber);
2892
- if (!row)
2893
- return null;
2894
- return this.rowToTask(row);
2903
+ return getTaskByReferenceNumberPolicy(this.getTaskReferencePolicyDeps(), referenceNumber, workspaceId);
2904
+ }
2905
+ getTaskByLocalReferenceNumber(referenceNumber, workspaceId) {
2906
+ return getTaskByLocalReferenceNumberPolicy(this.getTaskReferencePolicyDeps(), referenceNumber, workspaceId);
2895
2907
  }
2896
2908
  resolveTaskIdentifier(value, workspaceId) {
2897
- const normalizedValue = String(value || '').trim();
2898
- if (!normalizedValue)
2899
- return null;
2900
- const referenceNumber = parseTaskReference(normalizedValue);
2901
- if (referenceNumber) {
2902
- const exactReferenceMatch = this.getTaskByReferenceNumber(referenceNumber, workspaceId);
2903
- if (exactReferenceMatch)
2904
- return exactReferenceMatch;
2905
- }
2906
- return this.getTask(normalizedValue, workspaceId);
2909
+ return resolveTaskIdentifierPolicy(this.getTaskReferencePolicyDeps(), value, workspaceId);
2907
2910
  }
2908
2911
  getInitiative(id, workspaceId) {
2909
2912
  const normalizedId = String(id || '').trim();
@@ -3234,42 +3237,35 @@ export class TaskforceCore {
3234
3237
  });
3235
3238
  return this.listChecklistItems(normalizedTaskId, normalizedWorkspaceId);
3236
3239
  }
3237
- reserveTaskReferenceNumber(workspaceId, preferredReferenceNumber) {
3238
- const normalizedWorkspaceId = this.normalizeWorkspaceId(workspaceId);
3239
- const preferred = this.sanitizeTaskReferenceNumber(preferredReferenceNumber);
3240
- if (preferred) {
3241
- const existing = this.db.prepare(`
3242
- SELECT id
3243
- FROM tasks
3244
- WHERE tenant_id = ? AND workspace_id = ? AND reference_number = ?
3245
- LIMIT 1
3246
- `).get(this.tenantId, normalizedWorkspaceId, preferred);
3247
- if (!existing?.id) {
3248
- this.db.prepare(`
3249
- INSERT INTO workspace_task_reference_sequences (tenant_id, workspace_id, next_reference_number)
3250
- VALUES (?, ?, ?)
3251
- ON CONFLICT (tenant_id, workspace_id) DO UPDATE SET
3252
- next_reference_number = CASE
3253
- WHEN workspace_task_reference_sequences.next_reference_number < EXCLUDED.next_reference_number
3254
- THEN EXCLUDED.next_reference_number
3255
- ELSE workspace_task_reference_sequences.next_reference_number
3256
- END
3257
- `).run(this.tenantId, normalizedWorkspaceId, preferred);
3258
- return preferred;
3259
- }
3260
- }
3261
- const row = this.db.prepare(`
3262
- INSERT INTO workspace_task_reference_sequences (tenant_id, workspace_id, next_reference_number)
3263
- VALUES (?, ?, 1)
3264
- ON CONFLICT (tenant_id, workspace_id) DO UPDATE SET
3265
- next_reference_number = workspace_task_reference_sequences.next_reference_number + 1
3266
- RETURNING next_reference_number
3267
- `).get(this.tenantId, normalizedWorkspaceId);
3268
- const referenceNumber = this.sanitizeTaskReferenceNumber(row?.next_reference_number);
3269
- if (!referenceNumber) {
3270
- throw new TaskforceRuleError('Failed to allocate task reference number', 500, 'TASK_REFERENCE_ALLOC_FAILED');
3271
- }
3272
- return referenceNumber;
3240
+ getTaskReferenceSequenceValue(workspaceId) {
3241
+ return getTaskReferenceSequenceValuePolicy(this.getTaskReferencePolicyDeps(), workspaceId);
3242
+ }
3243
+ getLocalTaskReferenceSequenceValue(workspaceId) {
3244
+ return getLocalTaskReferenceSequenceValuePolicy(this.getTaskReferencePolicyDeps(), workspaceId);
3245
+ }
3246
+ getMaxTaskReferenceNumber(workspaceId, excludeTaskId) {
3247
+ return getMaxTaskReferenceNumberPolicy(this.getTaskReferencePolicyDeps(), workspaceId, excludeTaskId);
3248
+ }
3249
+ getMaxLocalTaskReferenceNumber(workspaceId, excludeTaskId) {
3250
+ return getMaxLocalTaskReferenceNumberPolicy(this.getTaskReferencePolicyDeps(), workspaceId, excludeTaskId);
3251
+ }
3252
+ persistTaskReferenceSequence(workspaceId, referenceNumber) {
3253
+ persistTaskReferenceSequencePolicy(this.getTaskReferencePolicyDeps(), workspaceId, referenceNumber);
3254
+ }
3255
+ persistLocalTaskReferenceSequence(workspaceId, referenceNumber) {
3256
+ persistLocalTaskReferenceSequencePolicy(this.getTaskReferencePolicyDeps(), workspaceId, referenceNumber);
3257
+ }
3258
+ allocateNextTaskReferenceNumber(workspaceId, excludeTaskId) {
3259
+ return allocateNextTaskReferenceNumberPolicy(this.getTaskReferencePolicyDeps(), workspaceId, excludeTaskId);
3260
+ }
3261
+ allocateNextLocalTaskReferenceNumber(workspaceId, excludeTaskId) {
3262
+ return allocateNextLocalTaskReferenceNumberPolicy(this.getTaskReferencePolicyDeps(), workspaceId, excludeTaskId);
3263
+ }
3264
+ relocateTaskReferenceConflict(workspaceId, referenceNumber, claimantTaskId, updatedAt) {
3265
+ return relocateTaskReferenceConflictPolicy(this.getTaskReferencePolicyDeps(), workspaceId, referenceNumber, claimantTaskId, updatedAt);
3266
+ }
3267
+ reserveTaskReferenceNumber(workspaceId, preferredReferenceNumber, currentTaskId) {
3268
+ return reserveTaskReferenceNumberPolicy(this.getTaskReferencePolicyDeps(), workspaceId, preferredReferenceNumber, currentTaskId);
3273
3269
  }
3274
3270
  reserveGenericReferenceNumber(params) {
3275
3271
  const normalizedWorkspaceId = this.normalizeWorkspaceId(params.workspaceId);
@@ -4060,7 +4056,7 @@ export class TaskforceCore {
4060
4056
  if (!normalizedUserId || normalizedUserId === 'anonymous')
4061
4057
  return null;
4062
4058
  const row = this.db.prepare(`
4063
- SELECT id, email, display_name, system_role, COALESCE(beta_access, 1) AS beta_access
4059
+ SELECT id, email, display_name, avatar_url, system_role, COALESCE(beta_access, 1) AS beta_access
4064
4060
  FROM users
4065
4061
  WHERE tenant_id = ? AND id = ?
4066
4062
  LIMIT 1
@@ -4071,13 +4067,17 @@ export class TaskforceCore {
4071
4067
  userId: row.id,
4072
4068
  email: row.email,
4073
4069
  displayName: row.display_name || null,
4070
+ avatarUrl: row.avatar_url || null,
4074
4071
  systemRole: this.normalizeSystemRole(row.system_role, 'user'),
4075
4072
  betaAccess: row.beta_access === undefined || row.beta_access === null ? true : Boolean(row.beta_access)
4076
4073
  };
4077
4074
  }
4078
- updateUserDisplayName(userId, displayName) {
4075
+ updateUserProfile(userId, input) {
4079
4076
  const normalizedUserId = String(userId || '').trim();
4080
- const trimmedDisplayName = String(displayName || '').trim();
4077
+ const trimmedDisplayName = String(input.displayName || '').trim();
4078
+ const normalizedAvatarUrl = input.avatarUrl === undefined
4079
+ ? undefined
4080
+ : (typeof input.avatarUrl === 'string' && input.avatarUrl.trim().length > 0 ? input.avatarUrl.trim() : null);
4081
4081
  if (!normalizedUserId)
4082
4082
  return null;
4083
4083
  if (!trimmedDisplayName) {
@@ -4086,15 +4086,145 @@ export class TaskforceCore {
4086
4086
  if (trimmedDisplayName.length > 120) {
4087
4087
  throw new TaskforceRuleError('Display name must be 120 characters or fewer', 400, 'DISPLAY_NAME_TOO_LONG');
4088
4088
  }
4089
- const result = this.db.prepare(`
4090
- UPDATE users
4091
- SET display_name = ?, updated_at = ?
4092
- WHERE tenant_id = ? AND id = ?
4093
- `).run(trimmedDisplayName, new Date().toISOString(), this.tenantId, normalizedUserId);
4089
+ const now = new Date().toISOString();
4090
+ const result = normalizedAvatarUrl === undefined
4091
+ ? this.db.prepare(`
4092
+ UPDATE users
4093
+ SET display_name = ?, updated_at = ?
4094
+ WHERE tenant_id = ? AND id = ?
4095
+ `).run(trimmedDisplayName, now, this.tenantId, normalizedUserId)
4096
+ : this.db.prepare(`
4097
+ UPDATE users
4098
+ SET display_name = ?, avatar_url = ?, updated_at = ?
4099
+ WHERE tenant_id = ? AND id = ?
4100
+ `).run(trimmedDisplayName, normalizedAvatarUrl, now, this.tenantId, normalizedUserId);
4094
4101
  if (Number(result?.changes || 0) <= 0)
4095
4102
  return null;
4096
4103
  return this.resolveUserIdentity(normalizedUserId);
4097
4104
  }
4105
+ updateUserDisplayName(userId, displayName) {
4106
+ return this.updateUserProfile(userId, { displayName });
4107
+ }
4108
+ createUserAvatarUploadDraft(input) {
4109
+ const draftId = String(input.draftId || '').trim();
4110
+ const userId = String(input.userId || '').trim();
4111
+ const storageKey = String(input.storageKey || '').trim();
4112
+ const mimeType = String(input.mimeType || 'application/octet-stream').trim() || 'application/octet-stream';
4113
+ const originalFilename = String(input.originalFilename || '').trim() || 'avatar';
4114
+ const sizeBytes = Number.isFinite(Number(input.sizeBytes)) ? Math.max(0, Math.floor(Number(input.sizeBytes))) : 0;
4115
+ const storageProvider = input.storageProvider === 'r2' || input.storageProvider === 'external' ? input.storageProvider : 'local';
4116
+ if (!draftId)
4117
+ throw new TaskforceRuleError('draftId is required', 400, 'AVATAR_DRAFT_ID_REQUIRED');
4118
+ if (!userId)
4119
+ throw new TaskforceRuleError('userId is required', 400, 'USER_ID_REQUIRED');
4120
+ if (!storageKey)
4121
+ throw new TaskforceRuleError('storageKey is required', 400, 'AVATAR_STORAGE_KEY_REQUIRED');
4122
+ const now = new Date().toISOString();
4123
+ this.db.prepare(`
4124
+ INSERT INTO user_avatar_upload_drafts (
4125
+ draft_id, tenant_id, user_id, storage_key, mime_type, original_filename, size_bytes, storage_provider,
4126
+ finalized_at, consumed_at, discarded_at, created_at, updated_at
4127
+ )
4128
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL, ?, ?)
4129
+ `).run(draftId, this.tenantId, userId, storageKey, mimeType, originalFilename, sizeBytes, storageProvider, now, now);
4130
+ return this.getUserAvatarUploadDraft(userId, draftId);
4131
+ }
4132
+ getUserAvatarUploadDraft(userId, draftId) {
4133
+ const normalizedUserId = String(userId || '').trim();
4134
+ const normalizedDraftId = String(draftId || '').trim();
4135
+ if (!normalizedUserId || !normalizedDraftId)
4136
+ return null;
4137
+ const row = this.db.prepare(`
4138
+ SELECT draft_id, user_id, storage_key, mime_type, original_filename, size_bytes, storage_provider,
4139
+ finalized_at, consumed_at, discarded_at, created_at, updated_at
4140
+ FROM user_avatar_upload_drafts
4141
+ WHERE tenant_id = ? AND user_id = ? AND draft_id = ?
4142
+ LIMIT 1
4143
+ `).get(this.tenantId, normalizedUserId, normalizedDraftId);
4144
+ if (!row?.draft_id)
4145
+ return null;
4146
+ return {
4147
+ draftId: String(row.draft_id),
4148
+ userId: String(row.user_id || normalizedUserId),
4149
+ storageKey: String(row.storage_key || ''),
4150
+ mimeType: String(row.mime_type || 'application/octet-stream'),
4151
+ originalFilename: String(row.original_filename || 'avatar'),
4152
+ sizeBytes: Number(row.size_bytes || 0),
4153
+ storageProvider: String(row.storage_provider || 'local') === 'r2'
4154
+ ? 'r2'
4155
+ : String(row.storage_provider || 'local') === 'external'
4156
+ ? 'external'
4157
+ : 'local',
4158
+ finalizedAt: row.finalized_at ? String(row.finalized_at) : null,
4159
+ consumedAt: row.consumed_at ? String(row.consumed_at) : null,
4160
+ discardedAt: row.discarded_at ? String(row.discarded_at) : null,
4161
+ createdAt: String(row.created_at || ''),
4162
+ updatedAt: String(row.updated_at || '')
4163
+ };
4164
+ }
4165
+ markUserAvatarUploadDraftFinalized(userId, draftId) {
4166
+ const existing = this.getUserAvatarUploadDraft(userId, draftId);
4167
+ if (!existing || existing.discardedAt)
4168
+ return null;
4169
+ const now = new Date().toISOString();
4170
+ this.db.prepare(`
4171
+ UPDATE user_avatar_upload_drafts
4172
+ SET finalized_at = COALESCE(finalized_at, ?), updated_at = ?
4173
+ WHERE tenant_id = ? AND user_id = ? AND draft_id = ? AND discarded_at IS NULL
4174
+ `).run(now, now, this.tenantId, existing.userId, existing.draftId);
4175
+ return this.getUserAvatarUploadDraft(existing.userId, existing.draftId);
4176
+ }
4177
+ markUserAvatarUploadDraftConsumed(userId, draftId) {
4178
+ const existing = this.getUserAvatarUploadDraft(userId, draftId);
4179
+ if (!existing || existing.discardedAt)
4180
+ return null;
4181
+ const now = new Date().toISOString();
4182
+ this.db.prepare(`
4183
+ UPDATE user_avatar_upload_drafts
4184
+ SET consumed_at = COALESCE(consumed_at, ?), updated_at = ?
4185
+ WHERE tenant_id = ? AND user_id = ? AND draft_id = ? AND discarded_at IS NULL
4186
+ `).run(now, now, this.tenantId, existing.userId, existing.draftId);
4187
+ return this.getUserAvatarUploadDraft(existing.userId, existing.draftId);
4188
+ }
4189
+ discardUserAvatarUploadDraft(userId, draftId) {
4190
+ const existing = this.getUserAvatarUploadDraft(userId, draftId);
4191
+ if (!existing || existing.discardedAt)
4192
+ return existing;
4193
+ const now = new Date().toISOString();
4194
+ this.db.prepare(`
4195
+ UPDATE user_avatar_upload_drafts
4196
+ SET discarded_at = ?, updated_at = ?
4197
+ WHERE tenant_id = ? AND user_id = ? AND draft_id = ? AND discarded_at IS NULL
4198
+ `).run(now, now, this.tenantId, existing.userId, existing.draftId);
4199
+ return this.getUserAvatarUploadDraft(existing.userId, existing.draftId);
4200
+ }
4201
+ cleanupStaleUserAvatarUploadDrafts(userId, olderThanIso) {
4202
+ const normalizedUserId = String(userId || '').trim();
4203
+ const cutoff = String(olderThanIso || '').trim();
4204
+ if (!normalizedUserId || !cutoff)
4205
+ return [];
4206
+ const rows = this.db.prepare(`
4207
+ SELECT draft_id
4208
+ FROM user_avatar_upload_drafts
4209
+ WHERE tenant_id = ?
4210
+ AND user_id = ?
4211
+ AND discarded_at IS NULL
4212
+ AND consumed_at IS NULL
4213
+ AND created_at < ?
4214
+ `).all(this.tenantId, normalizedUserId, cutoff);
4215
+ const staleDrafts = rows
4216
+ .map((row) => this.getUserAvatarUploadDraft(normalizedUserId, String(row?.draft_id || '')))
4217
+ .filter((row) => Boolean(row && !row.discardedAt));
4218
+ if (staleDrafts.length === 0)
4219
+ return [];
4220
+ const now = new Date().toISOString();
4221
+ this.db.prepare(`
4222
+ UPDATE user_avatar_upload_drafts
4223
+ SET discarded_at = ?, updated_at = ?
4224
+ WHERE tenant_id = ? AND user_id = ? AND discarded_at IS NULL AND consumed_at IS NULL AND created_at < ?
4225
+ `).run(now, now, this.tenantId, normalizedUserId, cutoff);
4226
+ return staleDrafts;
4227
+ }
4098
4228
  getSystemRole(userId) {
4099
4229
  const normalizedUserId = String(userId || '').trim();
4100
4230
  if (!normalizedUserId || normalizedUserId === 'anonymous')
@@ -5770,6 +5900,9 @@ export class TaskforceCore {
5770
5900
  this.assertRequiredTaskTaxonomies(normalizedInput.taxonomies, normalizedWorkspaceId);
5771
5901
  const incomingId = String(normalizedInput.id || '').trim();
5772
5902
  const incomingReferenceNumber = this.sanitizeTaskReferenceNumber(normalizedInput.referenceNumber);
5903
+ const useProvisionalLocalReference = options?.provisionalLocalReference === true
5904
+ && options?.allowClientProvidedId !== true
5905
+ && !incomingReferenceNumber;
5773
5906
  if (incomingId && !allowClientProvidedId) {
5774
5907
  throw new TaskforceRuleError('Client-supplied task id is not allowed for this operation.', 400, 'TASK_ID_NOT_ALLOWED');
5775
5908
  }
@@ -5777,7 +5910,15 @@ export class TaskforceCore {
5777
5910
  throw new TaskforceRuleError('Client-supplied task reference number is not allowed for this operation.', 400, 'TASK_REFERENCE_NOT_ALLOWED');
5778
5911
  }
5779
5912
  const id = incomingId || this.generateId();
5780
- const referenceNumber = this.reserveTaskReferenceNumber(normalizedWorkspaceId, incomingReferenceNumber);
5913
+ if (incomingReferenceNumber && options?.preferIncomingReferenceNumberOnConflict === true) {
5914
+ this.relocateTaskReferenceConflict(normalizedWorkspaceId, incomingReferenceNumber, id, options?.syncUpdatedAt);
5915
+ }
5916
+ const referenceNumber = useProvisionalLocalReference
5917
+ ? null
5918
+ : this.reserveTaskReferenceNumber(normalizedWorkspaceId, incomingReferenceNumber, id);
5919
+ const localReferenceNumber = useProvisionalLocalReference
5920
+ ? this.allocateNextLocalTaskReferenceNumber(normalizedWorkspaceId, id)
5921
+ : null;
5781
5922
  const normalizedCategory = normalizedInput.category || TaskforceCore.DEFAULT_CATEGORY_VALUE;
5782
5923
  const normalizedType = normalizedInput.type || this.resolveDefaultTypeValue(normalizedWorkspaceId);
5783
5924
  const createdByInput = String(normalizedInput.createdBy || '').trim() || undefined;
@@ -5816,7 +5957,8 @@ export class TaskforceCore {
5816
5957
  const task = {
5817
5958
  id,
5818
5959
  referenceNumber,
5819
- referenceLabel: formatTaskReference(referenceNumber),
5960
+ localReferenceNumber,
5961
+ referenceLabel: deriveTaskReferenceLabel({ referenceNumber, localReferenceNumber }),
5820
5962
  title: normalizedInput.title || 'Untitled Task',
5821
5963
  description: normalizedDescription || null,
5822
5964
  category: normalizedCategory,
@@ -5863,6 +6005,7 @@ export class TaskforceCore {
5863
6005
  id, tenant_id, title, description, status, priority, complexity, type, category,
5864
6006
  workspace_id,
5865
6007
  reference_number,
6008
+ local_reference_number,
5866
6009
  approach, created_at, updated_at, is_archived, taxonomies, created_by, assignee,
5867
6010
  scheduled_date, due_date, scheduled_week_key, order_in_day,
5868
6011
  workstream_id
@@ -5870,6 +6013,7 @@ export class TaskforceCore {
5870
6013
  @id, @tenantId, @title, @description, @status, @priority, @complexity, @type, @category,
5871
6014
  @workspaceId,
5872
6015
  @referenceNumber,
6016
+ @localReferenceNumber,
5873
6017
  @approach, @createdAt, @updatedAt, 0, @taxonomies, @createdBy, @assignee,
5874
6018
  @scheduledDate, @dueDate, @scheduledWeekKey, @orderInDay,
5875
6019
  @workstreamId
@@ -5946,7 +6090,8 @@ export class TaskforceCore {
5946
6090
  created.push(this.createTask(batch[i], {
5947
6091
  persistBackup: false,
5948
6092
  workspaceId: options?.workspaceId,
5949
- allowClientProvidedId: options?.allowClientProvidedId
6093
+ allowClientProvidedId: options?.allowClientProvidedId,
6094
+ provisionalLocalReference: options?.provisionalLocalReference
5950
6095
  }));
5951
6096
  }
5952
6097
  if (validateOnly) {
@@ -5973,7 +6118,8 @@ export class TaskforceCore {
5973
6118
  this.createTask(draft, {
5974
6119
  persistBackup: false,
5975
6120
  workspaceId: options?.workspaceId,
5976
- allowClientProvidedId: options?.allowClientProvidedId
6121
+ allowClientProvidedId: options?.allowClientProvidedId,
6122
+ provisionalLocalReference: options?.provisionalLocalReference
5977
6123
  });
5978
6124
  throw validateOnlyRollbackError;
5979
6125
  })(input);
@@ -5982,7 +6128,8 @@ export class TaskforceCore {
5982
6128
  created.push(this.createTask(input, {
5983
6129
  persistBackup: false,
5984
6130
  workspaceId: options?.workspaceId,
5985
- allowClientProvidedId: options?.allowClientProvidedId
6131
+ allowClientProvidedId: options?.allowClientProvidedId,
6132
+ provisionalLocalReference: options?.provisionalLocalReference
5986
6133
  }));
5987
6134
  }
5988
6135
  }
@@ -6075,7 +6222,19 @@ export class TaskforceCore {
6075
6222
  }
6076
6223
  }
6077
6224
  if (Object.prototype.hasOwnProperty.call(patch, 'referenceNumber')) {
6078
- patch.referenceNumber = this.sanitizeTaskReferenceNumber(patch.referenceNumber);
6225
+ const requestedReferenceNumber = this.sanitizeTaskReferenceNumber(patch.referenceNumber);
6226
+ if (requestedReferenceNumber && options?.preferIncomingReferenceNumberOnConflict === true) {
6227
+ this.relocateTaskReferenceConflict(normalizedWorkspaceId, requestedReferenceNumber, taskId, options?.syncUpdatedAt);
6228
+ }
6229
+ patch.referenceNumber = requestedReferenceNumber
6230
+ ? this.reserveTaskReferenceNumber(normalizedWorkspaceId, requestedReferenceNumber, taskId)
6231
+ : requestedReferenceNumber;
6232
+ if (requestedReferenceNumber) {
6233
+ patch.localReferenceNumber = null;
6234
+ }
6235
+ }
6236
+ if (Object.prototype.hasOwnProperty.call(patch, 'localReferenceNumber')) {
6237
+ patch.localReferenceNumber = this.sanitizeTaskReferenceNumber(patch.localReferenceNumber);
6079
6238
  }
6080
6239
  if (patch.status !== undefined) {
6081
6240
  patch.status = this.normalizeStatusValue(patch.status);
@@ -6093,7 +6252,10 @@ export class TaskforceCore {
6093
6252
  const updatedTask = {
6094
6253
  ...existing,
6095
6254
  ...patch,
6096
- referenceLabel: formatTaskReference(patch.referenceNumber ?? existing.referenceNumber),
6255
+ referenceLabel: deriveTaskReferenceLabel({
6256
+ referenceNumber: patch.referenceNumber ?? existing.referenceNumber,
6257
+ localReferenceNumber: patch.localReferenceNumber ?? existing.localReferenceNumber
6258
+ }),
6097
6259
  updatedAt: options?.syncUpdatedAt || patch.updatedAt || new Date().toISOString()
6098
6260
  };
6099
6261
  const fields = [];
@@ -6101,13 +6263,14 @@ export class TaskforceCore {
6101
6263
  const mappableFields = [
6102
6264
  'title', 'description', 'status', 'completedAt', 'priority', 'complexity', 'type', 'category',
6103
6265
  'approach', 'canceledReason', 'createdBy', 'assignee', 'scheduledDate', 'dueDate',
6104
- 'scheduledWeekKey', 'orderInDay', 'workstreamId', 'referenceNumber'
6266
+ 'scheduledWeekKey', 'orderInDay', 'workstreamId', 'referenceNumber', 'localReferenceNumber'
6105
6267
  ];
6106
6268
  const propertyToColumn = {
6107
6269
  canceledReason: 'canceled_reason',
6108
6270
  completedAt: 'completed_at',
6109
6271
  createdBy: 'created_by',
6110
6272
  referenceNumber: 'reference_number',
6273
+ localReferenceNumber: 'local_reference_number',
6111
6274
  scheduledDate: 'scheduled_date',
6112
6275
  dueDate: 'due_date',
6113
6276
  scheduledWeekKey: 'scheduled_week_key',
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,102 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { TaskforceCore } from './Taskforce';
6
+ function makeProjectRoot() {
7
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-user-avatar-'));
8
+ }
9
+ function seedPlans(core) {
10
+ core.upsertPlanCatalog({ planId: 'starter', displayName: 'Starter', status: 'active' });
11
+ core.updatePlanVersion({
12
+ planId: 'starter',
13
+ versionNumber: 1,
14
+ seatLimit: 1
15
+ });
16
+ core.updateGlobalSettings({
17
+ auth: {
18
+ signupMonetizationStrategy: 'auto_assign_default',
19
+ defaultSignupPlanVersionId: 'starter-v1'
20
+ }
21
+ });
22
+ }
23
+ function openCore(projectRoot) {
24
+ const core = new TaskforceCore({
25
+ projectRoot,
26
+ storagePath: path.join(projectRoot, '.taskforce')
27
+ });
28
+ seedPlans(core);
29
+ return core;
30
+ }
31
+ describe('TaskforceCore user profile avatar drafts', () => {
32
+ it('persists avatarUrl on profile updates and exposes it in resolved identity', () => {
33
+ const projectRoot = makeProjectRoot();
34
+ const core = openCore(projectRoot);
35
+ try {
36
+ const created = core.registerPasswordUser({
37
+ email: 'avatar-user@example.com',
38
+ password: 'password123'
39
+ });
40
+ const updated = core.updateUserProfile(created.userId, {
41
+ displayName: 'Avatar User',
42
+ avatarUrl: `/api/taskforce/context/${encodeURIComponent(`users/${created.userId}/profile/avatar/headshot.png`)}`
43
+ });
44
+ expect(updated).toMatchObject({
45
+ userId: created.userId,
46
+ displayName: 'Avatar User',
47
+ avatarUrl: `/api/taskforce/context/${encodeURIComponent(`users/${created.userId}/profile/avatar/headshot.png`)}`
48
+ });
49
+ expect(core.resolveUserIdentity(created.userId)?.avatarUrl).toBe(`/api/taskforce/context/${encodeURIComponent(`users/${created.userId}/profile/avatar/headshot.png`)}`);
50
+ }
51
+ finally {
52
+ core.close();
53
+ fs.rmSync(projectRoot, { recursive: true, force: true });
54
+ }
55
+ });
56
+ it('tracks, finalizes, discards, and cleans up stale avatar upload drafts', () => {
57
+ const projectRoot = makeProjectRoot();
58
+ const core = openCore(projectRoot);
59
+ try {
60
+ const created = core.registerPasswordUser({
61
+ email: 'draft-user@example.com',
62
+ password: 'password123'
63
+ });
64
+ const draft = core.createUserAvatarUploadDraft({
65
+ userId: created.userId,
66
+ draftId: 'avatar-draft-1',
67
+ storageKey: `users/${created.userId}/profile/avatar/avatar-draft-1-headshot.png`,
68
+ mimeType: 'image/png',
69
+ originalFilename: 'headshot.png',
70
+ sizeBytes: 1024,
71
+ storageProvider: 'r2'
72
+ });
73
+ expect(draft.finalizedAt).toBeNull();
74
+ expect(core.markUserAvatarUploadDraftFinalized(created.userId, draft.draftId)?.finalizedAt).toEqual(expect.any(String));
75
+ expect(core.markUserAvatarUploadDraftConsumed(created.userId, draft.draftId)?.consumedAt).toEqual(expect.any(String));
76
+ expect(core.discardUserAvatarUploadDraft(created.userId, draft.draftId)?.discardedAt).toEqual(expect.any(String));
77
+ core.createUserAvatarUploadDraft({
78
+ userId: created.userId,
79
+ draftId: 'avatar-draft-stale',
80
+ storageKey: `users/${created.userId}/profile/avatar/avatar-draft-stale-headshot.png`,
81
+ mimeType: 'image/png',
82
+ originalFilename: 'stale.png',
83
+ sizeBytes: 512,
84
+ storageProvider: 'r2'
85
+ });
86
+ const db = core.db;
87
+ db.prepare(`
88
+ UPDATE user_avatar_upload_drafts
89
+ SET created_at = ?, updated_at = ?
90
+ WHERE tenant_id = ? AND user_id = ? AND draft_id = ?
91
+ `).run('2026-01-01T00:00:00.000Z', '2026-01-01T00:00:00.000Z', core.getTenantId(), created.userId, 'avatar-draft-stale');
92
+ const stale = core.cleanupStaleUserAvatarUploadDrafts(created.userId, '2026-02-01T00:00:00.000Z');
93
+ expect(stale).toHaveLength(1);
94
+ expect(stale[0]?.draftId).toBe('avatar-draft-stale');
95
+ expect(core.getUserAvatarUploadDraft(created.userId, 'avatar-draft-stale')?.discardedAt).toEqual(expect.any(String));
96
+ }
97
+ finally {
98
+ core.close();
99
+ fs.rmSync(projectRoot, { recursive: true, force: true });
100
+ }
101
+ });
102
+ });
@@ -0,0 +1,28 @@
1
+ import type { DatabaseAdapter } from '../storage/databaseAdapter.js';
2
+ import type { TaskItem } from './Taskforce.js';
3
+ interface TaskReferencePolicyDeps<TTask> {
4
+ db: DatabaseAdapter;
5
+ tenantId: string;
6
+ normalizeWorkspaceId: (workspaceId?: string) => string;
7
+ sanitizeTaskReferenceNumber: (value: unknown) => number | null;
8
+ rowToTask: (row: any) => TTask;
9
+ getTask: (id: string, workspaceId?: string) => TTask | null;
10
+ }
11
+ export declare function getTaskReferenceSequenceValue<TTask>(deps: TaskReferencePolicyDeps<TTask>, workspaceId: string): number;
12
+ export declare function getLocalTaskReferenceSequenceValue<TTask>(deps: TaskReferencePolicyDeps<TTask>, workspaceId: string): number;
13
+ export declare function getMaxTaskReferenceNumber<TTask>(deps: TaskReferencePolicyDeps<TTask>, workspaceId: string, excludeTaskId?: string | null): number;
14
+ export declare function getMaxLocalTaskReferenceNumber<TTask>(deps: TaskReferencePolicyDeps<TTask>, workspaceId: string, excludeTaskId?: string | null): number;
15
+ export declare function persistTaskReferenceSequence<TTask>(deps: TaskReferencePolicyDeps<TTask>, workspaceId: string, referenceNumber: number): void;
16
+ export declare function persistLocalTaskReferenceSequence<TTask>(deps: TaskReferencePolicyDeps<TTask>, workspaceId: string, referenceNumber: number): void;
17
+ export declare function allocateNextTaskReferenceNumber<TTask>(deps: TaskReferencePolicyDeps<TTask>, workspaceId: string, excludeTaskId?: string | null): number;
18
+ export declare function allocateNextLocalTaskReferenceNumber<TTask>(deps: TaskReferencePolicyDeps<TTask>, workspaceId: string, excludeTaskId?: string | null): number;
19
+ export declare function relocateTaskReferenceConflict<TTask>(deps: TaskReferencePolicyDeps<TTask>, workspaceId: string, referenceNumber: number, claimantTaskId?: string | null, updatedAt?: string | null): {
20
+ taskId: string;
21
+ referenceNumber: number;
22
+ } | null;
23
+ export declare function reserveTaskReferenceNumber<TTask>(deps: TaskReferencePolicyDeps<TTask>, workspaceId: string, preferredReferenceNumber?: number | null, currentTaskId?: string | null): number;
24
+ export declare function getTaskByReferenceNumber<TTask>(deps: TaskReferencePolicyDeps<TTask>, referenceNumber: number, workspaceId?: string): TTask | null;
25
+ export declare function getTaskByLocalReferenceNumber<TTask>(deps: TaskReferencePolicyDeps<TTask>, referenceNumber: number, workspaceId?: string): TTask | null;
26
+ export declare function resolveTaskIdentifier(deps: TaskReferencePolicyDeps<TaskItem>, value: string, workspaceId?: string): TaskItem | null;
27
+ export declare function deriveTaskReferenceLabel(task: Pick<TaskItem, 'referenceNumber' | 'localReferenceNumber'>): string;
28
+ export {};