@taskforcehq/taskforce 0.3.168

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 (492) hide show
  1. package/README.md +241 -0
  2. package/dist/Taskforce.d.ts +3 -0
  3. package/dist/Taskforce.js +3 -0
  4. package/dist/Taskforce.module.css +7587 -0
  5. package/dist/TaskforceCore.CategoryFallback.test.d.ts +1 -0
  6. package/dist/TaskforceCore.CategoryFallback.test.js +98 -0
  7. package/dist/TaskforceCore.d.ts +12 -0
  8. package/dist/TaskforceCore.js +729 -0
  9. package/dist/TaskforceCore.test.d.ts +1 -0
  10. package/dist/TaskforceCore.test.js +2132 -0
  11. package/dist/TaskforceScreenshot.d.ts +11 -0
  12. package/dist/TaskforceScreenshot.js +113 -0
  13. package/dist/TaskforceWidget.d.ts +6 -0
  14. package/dist/TaskforceWidget.js +101 -0
  15. package/dist/TaskforceWidget.test.d.ts +1 -0
  16. package/dist/TaskforceWidget.test.js +92 -0
  17. package/dist/UnsavedChangesRelaxed.test.d.ts +1 -0
  18. package/dist/UnsavedChangesRelaxed.test.js +16 -0
  19. package/dist/__tests__/localizationCatalogs.test.d.ts +1 -0
  20. package/dist/__tests__/localizationCatalogs.test.js +37 -0
  21. package/dist/__tests__/planParser.test.d.ts +1 -0
  22. package/dist/__tests__/planParser.test.js +183 -0
  23. package/dist/__tests__/releaseVersion.test.d.ts +1 -0
  24. package/dist/__tests__/releaseVersion.test.js +32 -0
  25. package/dist/__tests__/structuredCommentValidation.test.d.ts +1 -0
  26. package/dist/__tests__/structuredCommentValidation.test.js +31 -0
  27. package/dist/__tests__/structuredDocValidation.test.d.ts +1 -0
  28. package/dist/__tests__/structuredDocValidation.test.js +30 -0
  29. package/dist/__tests__/templateEngine.test.d.ts +1 -0
  30. package/dist/__tests__/templateEngine.test.js +150 -0
  31. package/dist/__tests__/templates.test.d.ts +1 -0
  32. package/dist/__tests__/templates.test.js +45 -0
  33. package/dist/__tests__/workflowDocumentFixtures.test.d.ts +1 -0
  34. package/dist/__tests__/workflowDocumentFixtures.test.js +78 -0
  35. package/dist/__tests__/workflowExportMatrix.test.d.ts +1 -0
  36. package/dist/__tests__/workflowExportMatrix.test.js +113 -0
  37. package/dist/__tests__/workflowExportSnapshots.test.d.ts +1 -0
  38. package/dist/__tests__/workflowExportSnapshots.test.js +28 -0
  39. package/dist/__tests__/workflowGeneration.test.d.ts +1 -0
  40. package/dist/__tests__/workflowGeneration.test.js +12 -0
  41. package/dist/__tests__/workflowSimulation.test.d.ts +1 -0
  42. package/dist/__tests__/workflowSimulation.test.js +113 -0
  43. package/dist/__tests__/workflowV2Policy.test.d.ts +1 -0
  44. package/dist/__tests__/workflowV2Policy.test.js +80 -0
  45. package/dist/api.d.ts +30 -0
  46. package/dist/api.js +85 -0
  47. package/dist/cli.d.ts +2 -0
  48. package/dist/cli.js +217 -0
  49. package/dist/compat/priorityCompat.d.ts +2 -0
  50. package/dist/compat/priorityCompat.js +24 -0
  51. package/dist/components/features/DocumentGenerateModal.d.ts +9 -0
  52. package/dist/components/features/DocumentGenerateModal.js +114 -0
  53. package/dist/components/features/DocumentIndex.d.ts +19 -0
  54. package/dist/components/features/DocumentIndex.js +51 -0
  55. package/dist/components/features/DocumentViewer.d.ts +9 -0
  56. package/dist/components/features/DocumentViewer.js +253 -0
  57. package/dist/components/features/DocumentWorkspace.d.ts +21 -0
  58. package/dist/components/features/DocumentWorkspace.js +88 -0
  59. package/dist/components/features/TaskSettings.d.ts +209 -0
  60. package/dist/components/features/TaskSettings.js +500 -0
  61. package/dist/components/features/TaskSettings.test.d.ts +1 -0
  62. package/dist/components/features/TaskSettings.test.js +308 -0
  63. package/dist/components/features/settings/CategoryManager.d.ts +19 -0
  64. package/dist/components/features/settings/CategoryManager.js +113 -0
  65. package/dist/components/features/settings/TaxonomyEditor.d.ts +8 -0
  66. package/dist/components/features/settings/TaxonomyEditor.js +98 -0
  67. package/dist/components/features/settings/TaxonomyManager 2.d.ts +52 -0
  68. package/dist/components/features/settings/TaxonomyManager 2.js +75 -0
  69. package/dist/components/features/settings/TaxonomyManager.d.ts +54 -0
  70. package/dist/components/features/settings/TaxonomyManager.js +77 -0
  71. package/dist/components/features/settings/TypeManager.d.ts +10 -0
  72. package/dist/components/features/settings/TypeManager.js +50 -0
  73. package/dist/components/features/settings/WorkflowEditorPrototype.d.ts +15 -0
  74. package/dist/components/features/settings/WorkflowEditorPrototype.js +245 -0
  75. package/dist/components/task/TaskActionHeader.d.ts +20 -0
  76. package/dist/components/task/TaskActionHeader.js +13 -0
  77. package/dist/components/task/TaskCard.d.ts +47 -0
  78. package/dist/components/task/TaskCard.js +326 -0
  79. package/dist/components/task/TaskCard.test.d.ts +1 -0
  80. package/dist/components/task/TaskCard.test.js +95 -0
  81. package/dist/components/task/TaskContextUpload.d.ts +12 -0
  82. package/dist/components/task/TaskContextUpload.js +393 -0
  83. package/dist/components/task/TaskContextUpload.test.d.ts +1 -0
  84. package/dist/components/task/TaskContextUpload.test.js +189 -0
  85. package/dist/components/task/TaskForm.d.ts +105 -0
  86. package/dist/components/task/TaskForm.js +241 -0
  87. package/dist/components/task/TaskForm.test.d.ts +1 -0
  88. package/dist/components/task/TaskForm.test.js +288 -0
  89. package/dist/components/task/TaskKanban 2.d.ts +76 -0
  90. package/dist/components/task/TaskKanban 2.js +1004 -0
  91. package/dist/components/task/TaskKanban.d.ts +76 -0
  92. package/dist/components/task/TaskKanban.empty-column-drop.test.d.ts +1 -0
  93. package/dist/components/task/TaskKanban.empty-column-drop.test.js +66 -0
  94. package/dist/components/task/TaskKanban.js +1026 -0
  95. package/dist/components/task/TaskKanban.test.d.ts +1 -0
  96. package/dist/components/task/TaskKanban.test.js +46 -0
  97. package/dist/components/task/TaskList.d.ts +74 -0
  98. package/dist/components/task/TaskList.js +38 -0
  99. package/dist/components/task/TaskList.test.d.ts +1 -0
  100. package/dist/components/task/TaskList.test.js +270 -0
  101. package/dist/components/task/TaskScreenshotSection.d.ts +15 -0
  102. package/dist/components/task/TaskScreenshotSection.js +22 -0
  103. package/dist/components/ui/LevelSelector.d.ts +13 -0
  104. package/dist/components/ui/LevelSelector.js +25 -0
  105. package/dist/components/ui/LevelSelector.test.d.ts +1 -0
  106. package/dist/components/ui/LevelSelector.test.js +55 -0
  107. package/dist/components/ui/Markdown.d.ts +6 -0
  108. package/dist/components/ui/Markdown.js +94 -0
  109. package/dist/components/ui/Markdown.test.d.ts +1 -0
  110. package/dist/components/ui/Markdown.test.js +38 -0
  111. package/dist/components/ui/Modal.d.ts +16 -0
  112. package/dist/components/ui/Modal.js +49 -0
  113. package/dist/components/ui/MultiSelectFilter.d.ts +10 -0
  114. package/dist/components/ui/MultiSelectFilter.js +71 -0
  115. package/dist/components/ui/MultiSelectFilter.test.d.ts +1 -0
  116. package/dist/components/ui/MultiSelectFilter.test.js +74 -0
  117. package/dist/components/ui/TaxonomyDropdown.d.ts +17 -0
  118. package/dist/components/ui/TaxonomyDropdown.js +145 -0
  119. package/dist/components/ui/TaxonomyDropdown.test.d.ts +1 -0
  120. package/dist/components/ui/TaxonomyDropdown.test.js +130 -0
  121. package/dist/components/views/StandaloneLayout 2.d.ts +8 -0
  122. package/dist/components/views/StandaloneLayout 2.js +1660 -0
  123. package/dist/components/views/StandaloneLayout.d.ts +8 -0
  124. package/dist/components/views/StandaloneLayout.js +2117 -0
  125. package/dist/components/views/WidgetView.d.ts +8 -0
  126. package/dist/components/views/WidgetView.js +221 -0
  127. package/dist/config/envSchema.d.ts +28 -0
  128. package/dist/config/envSchema.js +374 -0
  129. package/dist/config/envSchema.test.d.ts +1 -0
  130. package/dist/config/envSchema.test.js +97 -0
  131. package/dist/config/surfaceBase.d.ts +2 -0
  132. package/dist/config/surfaceBase.js +8 -0
  133. package/dist/core/BulkUpdateFieldsPreview.test.d.ts +1 -0
  134. package/dist/core/BulkUpdateFieldsPreview.test.js +62 -0
  135. package/dist/core/CompletedAt.test.d.ts +1 -0
  136. package/dist/core/CompletedAt.test.js +44 -0
  137. package/dist/core/CreatedBy.test.d.ts +1 -0
  138. package/dist/core/CreatedBy.test.js +60 -0
  139. package/dist/core/EntitlementsPolicy.test.d.ts +1 -0
  140. package/dist/core/EntitlementsPolicy.test.js +145 -0
  141. package/dist/core/InitiativeTemplates.test.d.ts +1 -0
  142. package/dist/core/InitiativeTemplates.test.js +50 -0
  143. package/dist/core/ParentLifecycle.test.d.ts +1 -0
  144. package/dist/core/ParentLifecycle.test.js +98 -0
  145. package/dist/core/ScheduleFields.test.d.ts +1 -0
  146. package/dist/core/ScheduleFields.test.js +197 -0
  147. package/dist/core/SetupState.test.d.ts +1 -0
  148. package/dist/core/SetupState.test.js +113 -0
  149. package/dist/core/SystemAdmin.test.d.ts +1 -0
  150. package/dist/core/SystemAdmin.test.js +295 -0
  151. package/dist/core/TaskTaxonomyValidation.test.d.ts +1 -0
  152. package/dist/core/TaskTaxonomyValidation.test.js +64 -0
  153. package/dist/core/Taskforce.d.ts +1063 -0
  154. package/dist/core/Taskforce.ids.test.d.ts +1 -0
  155. package/dist/core/Taskforce.ids.test.js +168 -0
  156. package/dist/core/Taskforce.js +5981 -0
  157. package/dist/core/Taskforce.realtimeMutation.test.d.ts +1 -0
  158. package/dist/core/Taskforce.realtimeMutation.test.js +76 -0
  159. package/dist/core/WorkspaceDelete.test.d.ts +1 -0
  160. package/dist/core/WorkspaceDelete.test.js +140 -0
  161. package/dist/core/WorkspacePermissions.test.d.ts +1 -0
  162. package/dist/core/WorkspacePermissions.test.js +151 -0
  163. package/dist/core/WorkspacePlanMode.test.d.ts +1 -0
  164. package/dist/core/WorkspacePlanMode.test.js +104 -0
  165. package/dist/hooks/authGuardPolicy.d.ts +26 -0
  166. package/dist/hooks/authGuardPolicy.js +32 -0
  167. package/dist/hooks/authGuardPolicy.test.d.ts +1 -0
  168. package/dist/hooks/authGuardPolicy.test.js +76 -0
  169. package/dist/hooks/useDraggable.d.ts +12 -0
  170. package/dist/hooks/useDraggable.js +65 -0
  171. package/dist/hooks/useRealtimeSync.d.ts +24 -0
  172. package/dist/hooks/useRealtimeSync.js +275 -0
  173. package/dist/hooks/useRealtimeSync.test.d.ts +1 -0
  174. package/dist/hooks/useRealtimeSync.test.js +284 -0
  175. package/dist/hooks/useSyncOrchestrator.d.ts +164 -0
  176. package/dist/hooks/useSyncOrchestrator.js +1427 -0
  177. package/dist/hooks/useSyncOrchestrator.retry-closure.test.d.ts +1 -0
  178. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +367 -0
  179. package/dist/hooks/useTaskforce.d.ts +727 -0
  180. package/dist/hooks/useTaskforce.js +4396 -0
  181. package/dist/hooks/useTaskforce.priority-filters.test.d.ts +1 -0
  182. package/dist/hooks/useTaskforce.priority-filters.test.js +33 -0
  183. package/dist/hooks/useWorkspaceSyncController.d.ts +80 -0
  184. package/dist/hooks/useWorkspaceSyncController.js +598 -0
  185. package/dist/hooks/useWorkspaceSyncController.test.d.ts +1 -0
  186. package/dist/hooks/useWorkspaceSyncController.test.js +236 -0
  187. package/dist/index.d.ts +12 -0
  188. package/dist/index.js +11 -0
  189. package/dist/localization/index.d.ts +8 -0
  190. package/dist/localization/index.js +81 -0
  191. package/dist/localization/locales/en-US.d.ts +151 -0
  192. package/dist/localization/locales/en-US.js +150 -0
  193. package/dist/localization/locales/es-419.d.ts +2 -0
  194. package/dist/localization/locales/es-419.js +150 -0
  195. package/dist/localization/locales/pt-BR.d.ts +2 -0
  196. package/dist/localization/locales/pt-BR.js +150 -0
  197. package/dist/main.d.ts +1 -0
  198. package/dist/main.js +33 -0
  199. package/dist/mcp/optionResolution.d.ts +14 -0
  200. package/dist/mcp/optionResolution.js +126 -0
  201. package/dist/mcp/optionResolution.test.d.ts +1 -0
  202. package/dist/mcp/optionResolution.test.js +39 -0
  203. package/dist/mcp/server.d.ts +1 -0
  204. package/dist/mcp/server.js +1793 -0
  205. package/dist/mcp/structuredCommentValidation.d.ts +6 -0
  206. package/dist/mcp/structuredCommentValidation.js +56 -0
  207. package/dist/mcp/structuredDocValidation.d.ts +6 -0
  208. package/dist/mcp/structuredDocValidation.js +75 -0
  209. package/dist/migrations/billingSchemaParity.test.d.ts +1 -0
  210. package/dist/migrations/billingSchemaParity.test.js +46 -0
  211. package/dist/migrations/clientMigrations.d.ts +9 -0
  212. package/dist/migrations/clientMigrations.js +74 -0
  213. package/dist/migrations/flags.d.ts +6 -0
  214. package/dist/migrations/flags.js +8 -0
  215. package/dist/migrations/taskMigrations.d.ts +71 -0
  216. package/dist/migrations/taskMigrations.js +236 -0
  217. package/dist/migrations/taskMigrations.test.d.ts +1 -0
  218. package/dist/migrations/taskMigrations.test.js +34 -0
  219. package/dist/migrations/taskSchemaMigrations.d.ts +11 -0
  220. package/dist/migrations/taskSchemaMigrations.js +453 -0
  221. package/dist/plugins/index.d.ts +10 -0
  222. package/dist/plugins/index.js +10 -0
  223. package/dist/plugins/vite.d.ts +19 -0
  224. package/dist/plugins/vite.js +271 -0
  225. package/dist/public/android-chrome-192x192.png +0 -0
  226. package/dist/public/android-chrome-512x512.png +0 -0
  227. package/dist/public/apple-touch-icon.png +0 -0
  228. package/dist/public/favicon-16x16.png +0 -0
  229. package/dist/public/favicon-32x32.png +0 -0
  230. package/dist/public/favicon.ico +0 -0
  231. package/dist/public/favicon.png +0 -0
  232. package/dist/public/site.webmanifest +1 -0
  233. package/dist/public/taskforce.png +0 -0
  234. package/dist/resources/templates/WORKFLOW_V2_POLICY.md +55 -0
  235. package/dist/resources/templates/environments/aider.yaml +14 -0
  236. package/dist/resources/templates/environments/antigravity.yaml +10 -0
  237. package/dist/resources/templates/environments/claude.yaml +10 -0
  238. package/dist/resources/templates/environments/cline.yaml +10 -0
  239. package/dist/resources/templates/environments/codex.yaml +13 -0
  240. package/dist/resources/templates/environments/copilot.yaml +10 -0
  241. package/dist/resources/templates/environments/cursor.yaml +9 -0
  242. package/dist/resources/templates/environments/gemini.yaml +9 -0
  243. package/dist/resources/templates/environments/windsurf.yaml +10 -0
  244. package/dist/resources/templates/workflow-sources/README.md +9 -0
  245. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +520 -0
  246. package/dist/resources/templates/workflow-sources/workflowEditorOverrides.json +1 -0
  247. package/dist/resources/templates/workflows/collaborate.yaml +104 -0
  248. package/dist/resources/templates/workflows/do.yaml +82 -0
  249. package/dist/resources/templates/workflows/evaluate.yaml +111 -0
  250. package/dist/resources/templates/workflows/execute.yaml +142 -0
  251. package/dist/resources/templates/workflows/plan.yaml +112 -0
  252. package/dist/resources/templates/workflows/review.yaml +109 -0
  253. package/dist/runtime/capabilities.d.ts +8 -0
  254. package/dist/runtime/capabilities.js +16 -0
  255. package/dist/security/virusScan.d.ts +16 -0
  256. package/dist/security/virusScan.js +83 -0
  257. package/dist/server/auth.d.ts +51 -0
  258. package/dist/server/auth.js +266 -0
  259. package/dist/server/auth.test.d.ts +1 -0
  260. package/dist/server/auth.test.js +111 -0
  261. package/dist/server/buildInfo.d.ts +7 -0
  262. package/dist/server/buildInfo.js +100 -0
  263. package/dist/server/buildInfo.test.d.ts +1 -0
  264. package/dist/server/buildInfo.test.js +29 -0
  265. package/dist/server/cors.d.ts +7 -0
  266. package/dist/server/cors.js +76 -0
  267. package/dist/server/cors.test.d.ts +1 -0
  268. package/dist/server/cors.test.js +71 -0
  269. package/dist/server/email.d.ts +28 -0
  270. package/dist/server/email.js +138 -0
  271. package/dist/server/email.test.d.ts +1 -0
  272. package/dist/server/email.test.js +113 -0
  273. package/dist/server/index.cookieProxy.test.d.ts +1 -0
  274. package/dist/server/index.cookieProxy.test.js +19 -0
  275. package/dist/server/index.d.ts +17 -0
  276. package/dist/server/index.js +757 -0
  277. package/dist/server/index.rateLimit.test.d.ts +1 -0
  278. package/dist/server/index.rateLimit.test.js +13 -0
  279. package/dist/server/index.test.d.ts +1 -0
  280. package/dist/server/index.test.js +19 -0
  281. package/dist/server/rateLimit.d.ts +29 -0
  282. package/dist/server/rateLimit.js +99 -0
  283. package/dist/server/rateLimit.test.d.ts +1 -0
  284. package/dist/server/rateLimit.test.js +107 -0
  285. package/dist/server/realtimeSync.auth.test.d.ts +1 -0
  286. package/dist/server/realtimeSync.auth.test.js +62 -0
  287. package/dist/server/realtimeSync.replay.test.d.ts +1 -0
  288. package/dist/server/realtimeSync.replay.test.js +192 -0
  289. package/dist/server/realtimeSync.workspaceIsolation.test.d.ts +1 -0
  290. package/dist/server/realtimeSync.workspaceIsolation.test.js +101 -0
  291. package/dist/server/realtimeSyncWs.d.ts +30 -0
  292. package/dist/server/realtimeSyncWs.js +257 -0
  293. package/dist/server/routes.billing.d.ts +15 -0
  294. package/dist/server/routes.billing.js +991 -0
  295. package/dist/server/routes.billing.test.d.ts +1 -0
  296. package/dist/server/routes.billing.test.js +387 -0
  297. package/dist/server/routes.d.ts +72 -0
  298. package/dist/server/routes.js +4820 -0
  299. package/dist/server/routes.sync.d.ts +17 -0
  300. package/dist/server/routes.sync.integration.test.d.ts +1 -0
  301. package/dist/server/routes.sync.integration.test.js +470 -0
  302. package/dist/server/routes.sync.js +1141 -0
  303. package/dist/server/routes.test.d.ts +1 -0
  304. package/dist/server/routes.test.js +4945 -0
  305. package/dist/server/runtimeMode.d.ts +4 -0
  306. package/dist/server/runtimeMode.js +25 -0
  307. package/dist/server/runtimeMode.test.d.ts +1 -0
  308. package/dist/server/runtimeMode.test.js +26 -0
  309. package/dist/server/securityHeaders.d.ts +3 -0
  310. package/dist/server/securityHeaders.js +25 -0
  311. package/dist/server/securityHeaders.test.d.ts +1 -0
  312. package/dist/server/securityHeaders.test.js +32 -0
  313. package/dist/server/smtpTransport.d.ts +21 -0
  314. package/dist/server/smtpTransport.js +11 -0
  315. package/dist/server/start.d.ts +1 -0
  316. package/dist/server/start.js +17 -0
  317. package/dist/services/formatTransformers.d.ts +29 -0
  318. package/dist/services/formatTransformers.js +56 -0
  319. package/dist/services/templateEngine.d.ts +46 -0
  320. package/dist/services/templateEngine.js +148 -0
  321. package/dist/shared/runtimeContract.d.ts +12 -0
  322. package/dist/shared/runtimeContract.js +23 -0
  323. package/dist/shared/runtimeContract.test.d.ts +1 -0
  324. package/dist/shared/runtimeContract.test.js +23 -0
  325. package/dist/soak/preflight.cli.d.ts +1 -0
  326. package/dist/soak/preflight.cli.js +28 -0
  327. package/dist/soak/preflight.d.ts +26 -0
  328. package/dist/soak/preflight.js +113 -0
  329. package/dist/soak/preflight.test.d.ts +1 -0
  330. package/dist/soak/preflight.test.js +39 -0
  331. package/dist/soak/syncSoakHarness.d.ts +6 -0
  332. package/dist/soak/syncSoakHarness.js +10 -0
  333. package/dist/storage/createDatabaseAdapter.d.ts +2 -0
  334. package/dist/storage/createDatabaseAdapter.js +9 -0
  335. package/dist/storage/databaseAdapter.d.ts +22 -0
  336. package/dist/storage/databaseAdapter.js +1 -0
  337. package/dist/storage/documentIntegrity.d.ts +28 -0
  338. package/dist/storage/documentIntegrity.js +103 -0
  339. package/dist/storage/documentPurge.d.ts +14 -0
  340. package/dist/storage/documentPurge.js +46 -0
  341. package/dist/storage/documentRegistry.d.ts +39 -0
  342. package/dist/storage/documentRegistry.js +115 -0
  343. package/dist/storage/integritySchedule.d.ts +17 -0
  344. package/dist/storage/integritySchedule.js +62 -0
  345. package/dist/storage/objectStorage.d.ts +20 -0
  346. package/dist/storage/objectStorage.js +15 -0
  347. package/dist/storage/objectStorageClient.d.ts +18 -0
  348. package/dist/storage/objectStorageClient.js +301 -0
  349. package/dist/storage/objectStorageConfig.d.ts +2 -0
  350. package/dist/storage/objectStorageConfig.js +45 -0
  351. package/dist/storage/postgresAdapter.d.ts +20 -0
  352. package/dist/storage/postgresAdapter.js +204 -0
  353. package/dist/storage/postgresWorker.d.ts +1 -0
  354. package/dist/storage/postgresWorker.js +110 -0
  355. package/dist/storage/providerConfig.d.ts +6 -0
  356. package/dist/storage/providerConfig.js +11 -0
  357. package/dist/storage/sqliteAdapter.d.ts +14 -0
  358. package/dist/storage/sqliteAdapter.js +28 -0
  359. package/dist/storage/taskAssetStore.d.ts +56 -0
  360. package/dist/storage/taskAssetStore.js +297 -0
  361. package/dist/sync/cloudSyncApi.d.ts +47 -0
  362. package/dist/sync/cloudSyncApi.js +138 -0
  363. package/dist/sync/cloudSyncApi.test.d.ts +1 -0
  364. package/dist/sync/cloudSyncApi.test.js +54 -0
  365. package/dist/sync/encryptionService.d.ts +11 -0
  366. package/dist/sync/encryptionService.js +61 -0
  367. package/dist/sync/encryptionService.test.d.ts +1 -0
  368. package/dist/sync/encryptionService.test.js +52 -0
  369. package/dist/sync/realtimeFeature.d.ts +6 -0
  370. package/dist/sync/realtimeFeature.js +20 -0
  371. package/dist/sync/realtimeFeature.test.d.ts +1 -0
  372. package/dist/sync/realtimeFeature.test.js +30 -0
  373. package/dist/sync/realtimeReconcile.d.ts +30 -0
  374. package/dist/sync/realtimeReconcile.js +90 -0
  375. package/dist/sync/realtimeReconcile.test.d.ts +1 -0
  376. package/dist/sync/realtimeReconcile.test.js +43 -0
  377. package/dist/sync/syncService.d.ts +96 -0
  378. package/dist/sync/syncService.js +423 -0
  379. package/dist/sync/syncService.test.d.ts +1 -0
  380. package/dist/sync/syncService.test.js +484 -0
  381. package/dist/sync/workspaceSyncModel.d.ts +61 -0
  382. package/dist/sync/workspaceSyncModel.js +136 -0
  383. package/dist/sync/workspaceSyncModel.test.d.ts +1 -0
  384. package/dist/sync/workspaceSyncModel.test.js +73 -0
  385. package/dist/test/setup.d.ts +1 -0
  386. package/dist/test/setup.js +15 -0
  387. package/dist/types.d.ts +157 -0
  388. package/dist/types.js +41 -0
  389. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  390. package/dist/ui/assets/index-0W5vyCpn.css +1 -0
  391. package/dist/ui/assets/index-MCZuNNfn.js +23 -0
  392. package/dist/ui/assets/logo_white_01-0Vx-iIIv.png +0 -0
  393. package/dist/ui/assets/taskforce-BxLPokNB.png +0 -0
  394. package/dist/ui/assets/vendor-dnd-BI1K77C1.js +5 -0
  395. package/dist/ui/assets/vendor-icons-ZFG3SGwo.js +1 -0
  396. package/dist/ui/assets/vendor-markdown-CKYkQHfj.js +29 -0
  397. package/dist/ui/assets/vendor-react-DdIDsh7f.js +9 -0
  398. package/dist/ui/assets/vendor-router-BtiNU7Dl.js +3 -0
  399. package/dist/ui/branding/logos/logo_black_01.png +0 -0
  400. package/dist/ui/branding/logos/logo_black_02.png +0 -0
  401. package/dist/ui/branding/logos/logo_white_01.png +0 -0
  402. package/dist/ui/branding/logos/logo_white_02.png +0 -0
  403. package/dist/ui/favicon/android-chrome-192x192.png +0 -0
  404. package/dist/ui/favicon/android-chrome-512x512.png +0 -0
  405. package/dist/ui/favicon/apple-touch-icon.png +0 -0
  406. package/dist/ui/favicon/favicon-16x16.png +0 -0
  407. package/dist/ui/favicon/favicon-32x32.png +0 -0
  408. package/dist/ui/favicon/favicon.ico +0 -0
  409. package/dist/ui/favicon/favicon.png +0 -0
  410. package/dist/ui/favicon/site.webmanifest +1 -0
  411. package/dist/ui/fonts/RussoOne-Regular.ttf +0 -0
  412. package/dist/ui/images/taskforce.png +0 -0
  413. package/dist/ui/index.html +19 -0
  414. package/dist/utils/checklistMarkdown.d.ts +9 -0
  415. package/dist/utils/checklistMarkdown.js +46 -0
  416. package/dist/utils/constants.d.ts +21 -0
  417. package/dist/utils/constants.icons.test.d.ts +1 -0
  418. package/dist/utils/constants.icons.test.js +40 -0
  419. package/dist/utils/constants.js +100 -0
  420. package/dist/utils/contextFiles.d.ts +8 -0
  421. package/dist/utils/contextFiles.js +54 -0
  422. package/dist/utils/formatting.d.ts +5 -0
  423. package/dist/utils/formatting.js +30 -0
  424. package/dist/utils/planParser.d.ts +22 -0
  425. package/dist/utils/planParser.js +151 -0
  426. package/dist/utils/taskDragZones.d.ts +8 -0
  427. package/dist/utils/taskDragZones.js +14 -0
  428. package/dist/utils/taskDragZones.test.d.ts +1 -0
  429. package/dist/utils/taskDragZones.test.js +14 -0
  430. package/dist/utils/taskHierarchy.d.ts +4 -0
  431. package/dist/utils/taskHierarchy.js +69 -0
  432. package/dist/utils/taskHierarchy.test.d.ts +1 -0
  433. package/dist/utils/taskHierarchy.test.js +38 -0
  434. package/dist/utils/taskParenting.d.ts +3 -0
  435. package/dist/utils/taskParenting.js +24 -0
  436. package/dist/utils/taskParenting.test.d.ts +1 -0
  437. package/dist/utils/taskParenting.test.js +21 -0
  438. package/dist/utils/taskRelations.d.ts +6 -0
  439. package/dist/utils/taskRelations.js +24 -0
  440. package/dist/utils/taskRelations.test.d.ts +1 -0
  441. package/dist/utils/taskRelations.test.js +34 -0
  442. package/dist/utils/taskUtils.d.ts +2 -0
  443. package/dist/utils/taskUtils.js +20 -0
  444. package/dist/utils/taskforceApiClient.d.ts +7 -0
  445. package/dist/utils/taskforceApiClient.js +42 -0
  446. package/dist/utils/workspaceIdentity.d.ts +8 -0
  447. package/dist/utils/workspaceIdentity.js +47 -0
  448. package/dist/utils/workspaceIdentity.test.d.ts +1 -0
  449. package/dist/utils/workspaceIdentity.test.js +32 -0
  450. package/dist/utils/workspaceLifecycle.d.ts +26 -0
  451. package/dist/utils/workspaceLifecycle.js +69 -0
  452. package/dist/utils/workspaceLifecycle.test.d.ts +1 -0
  453. package/dist/utils/workspaceLifecycle.test.js +64 -0
  454. package/package.json +133 -0
  455. package/scripts/check-compat-boundaries.js +48 -0
  456. package/scripts/check-env.ts +24 -0
  457. package/scripts/check-localization-strings.mjs +49 -0
  458. package/scripts/check-runtime-boundaries.mjs +44 -0
  459. package/scripts/debug-global.mjs +34 -0
  460. package/scripts/debug-server.mjs +36 -0
  461. package/scripts/export-sqlite-to-postgres.mjs +125 -0
  462. package/scripts/generate-env-example.ts +8 -0
  463. package/scripts/generate-workflow-templates.mjs +134 -0
  464. package/scripts/mcp-stdio-adapter.mjs +160 -0
  465. package/scripts/postinstall.js +60 -0
  466. package/scripts/release-version-lib.mjs +88 -0
  467. package/scripts/release-version.mjs +72 -0
  468. package/scripts/run-dev-proxy.sh +49 -0
  469. package/scripts/run-e2e-realtime.sh +16 -0
  470. package/scripts/run-smoke.sh +23 -0
  471. package/scripts/run-soak.sh +29 -0
  472. package/scripts/test-global.js +143 -0
  473. package/scripts/update-homebrew-formula.sh +38 -0
  474. package/src/resources/templates/WORKFLOW_V2_POLICY.md +55 -0
  475. package/src/resources/templates/environments/aider.yaml +14 -0
  476. package/src/resources/templates/environments/antigravity.yaml +10 -0
  477. package/src/resources/templates/environments/claude.yaml +10 -0
  478. package/src/resources/templates/environments/cline.yaml +10 -0
  479. package/src/resources/templates/environments/codex.yaml +13 -0
  480. package/src/resources/templates/environments/copilot.yaml +10 -0
  481. package/src/resources/templates/environments/cursor.yaml +9 -0
  482. package/src/resources/templates/environments/gemini.yaml +9 -0
  483. package/src/resources/templates/environments/windsurf.yaml +10 -0
  484. package/src/resources/templates/workflow-sources/README.md +9 -0
  485. package/src/resources/templates/workflow-sources/workflowDocs.mjs +520 -0
  486. package/src/resources/templates/workflow-sources/workflowEditorOverrides.json +1 -0
  487. package/src/resources/templates/workflows/collaborate.yaml +104 -0
  488. package/src/resources/templates/workflows/do.yaml +82 -0
  489. package/src/resources/templates/workflows/evaluate.yaml +111 -0
  490. package/src/resources/templates/workflows/execute.yaml +142 -0
  491. package/src/resources/templates/workflows/plan.yaml +112 -0
  492. package/src/resources/templates/workflows/review.yaml +109 -0
@@ -0,0 +1,17 @@
1
+ import { IncomingMessage } from 'http';
2
+ import { TaskforceCore } from '../core/Taskforce.js';
3
+ import { RouteHandler, RouteContext } from './routes.js';
4
+ import { resolveRequestAccess, createSessionTokenCookie } from './auth.js';
5
+ import { isReservedWorkspaceId } from '../utils/workspaceIdentity.js';
6
+ export interface SyncRouteDependencies {
7
+ addRoute: (method: string, path: string, handler: RouteHandler) => void;
8
+ core: TaskforceCore;
9
+ context: RouteContext;
10
+ auditAuthAction: (req: IncomingMessage, action: string, details: Record<string, unknown>) => void;
11
+ resolveErrorStatusCode: (error: unknown, fallback?: number) => number;
12
+ requestWorkspaceId: (req: IncomingMessage) => string | null;
13
+ resolveRequestAccess: typeof resolveRequestAccess;
14
+ isReservedWorkspaceId: typeof isReservedWorkspaceId;
15
+ createSessionTokenCookie: typeof createSessionTokenCookie;
16
+ }
17
+ export declare function registerSyncRoutes(deps: SyncRouteDependencies): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,470 @@
1
+ import { describe, it, expect, afterEach, vi } from 'vitest';
2
+ import { Readable } from 'stream';
3
+ import * as fs from 'fs';
4
+ import * as os from 'os';
5
+ import * as path from 'path';
6
+ import { TaskforceCore } from '../core/Taskforce';
7
+ import { createRoutes, matchRoute } from './routes';
8
+ import { createSessionTokenCookie } from './auth';
9
+ function createMockReq(method, url, body, headers) {
10
+ const req = new Readable();
11
+ req._read = () => { };
12
+ req.push(body ? JSON.stringify(body) : null);
13
+ req.push(null);
14
+ req.method = method;
15
+ req.url = url;
16
+ req.headers = headers || {};
17
+ return req;
18
+ }
19
+ function createMockRes() {
20
+ return {
21
+ writeHead: vi.fn(),
22
+ end: vi.fn()
23
+ };
24
+ }
25
+ describe('Server Routes Workspace Sync Integration', () => {
26
+ const tempDirs = [];
27
+ const originalHome = process.env.HOME;
28
+ const workspaceId = 'workspace-local-active';
29
+ const authConfig = {
30
+ runtimeMode: 'cloud',
31
+ enabled: true,
32
+ requiredForApi: true,
33
+ excludedPaths: ['/api/taskforce/health', '/api/taskforce/version', '/api/taskforce/auth'],
34
+ defaultRole: 'member',
35
+ defaultWorkspaceId: '',
36
+ sessionCookieName: 'taskforce_session',
37
+ sessionSecret: 'integration-test-secret',
38
+ sessionTtlSeconds: 60 * 60,
39
+ sessionCookieSameSite: 'None',
40
+ sessionCookieSecure: true
41
+ };
42
+ const createAuthCookie = (overrideWorkspaceId) => createSessionTokenCookie('session-sync-integration', authConfig, {
43
+ role: 'owner',
44
+ workspaceId: overrideWorkspaceId || workspaceId,
45
+ userId: 'integration-user'
46
+ });
47
+ const readJsonBody = (res) => JSON.parse(String(res.end.mock.calls.at(-1)?.[0] || '{}'));
48
+ const createIsolatedProjectRoot = (prefix) => {
49
+ const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
50
+ tempDirs.push(projectRoot);
51
+ process.env.HOME = projectRoot;
52
+ return projectRoot;
53
+ };
54
+ afterEach(() => {
55
+ process.env.HOME = originalHome;
56
+ while (tempDirs.length > 0) {
57
+ const dir = tempDirs.pop();
58
+ if (!dir)
59
+ continue;
60
+ fs.rmSync(dir, { recursive: true, force: true });
61
+ }
62
+ });
63
+ it('persists cloud-pulled tasks into local sqlite via apply-local route', async () => {
64
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-');
65
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
66
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
67
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
68
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
69
+ expect(match).toBeTruthy();
70
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
71
+ changes: [
72
+ {
73
+ op: 'upsert',
74
+ archived: false,
75
+ task: {
76
+ id: 'task-cloud-1',
77
+ title: 'Task from cloud',
78
+ status: 'in-progress',
79
+ canceledReason: null
80
+ }
81
+ }
82
+ ]
83
+ }, { 'x-taskforce-workspace-id': workspaceId });
84
+ const res = createMockRes();
85
+ await match?.handler(req, res, {});
86
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
87
+ // Reload from disk to assert persistence, not just in-memory state.
88
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
89
+ const persisted = reloadedCore.listTasks(workspaceId);
90
+ const task = persisted.tasks.find((item) => item.id === 'task-cloud-1');
91
+ const defaultWorkspaceTasks = reloadedCore.listTasks('default').tasks;
92
+ expect(task).toBeTruthy();
93
+ expect(task?.title).toBe('Task from cloud');
94
+ expect(task?.status).toBe('in-progress');
95
+ expect(defaultWorkspaceTasks.find((item) => item.id === 'task-cloud-1')).toBeUndefined();
96
+ });
97
+ it('persists archived cloud-pulled tasks into local sqlite archive via apply-local route', async () => {
98
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-archived-');
99
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
100
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
101
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
102
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
103
+ expect(match).toBeTruthy();
104
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
105
+ changes: [
106
+ {
107
+ op: 'upsert',
108
+ archived: true,
109
+ task: {
110
+ id: 'task-cloud-archived-1',
111
+ title: 'Archived from cloud',
112
+ status: 'done',
113
+ isArchived: true
114
+ }
115
+ }
116
+ ]
117
+ }, { 'x-taskforce-workspace-id': workspaceId });
118
+ const res = createMockRes();
119
+ await match?.handler(req, res, {});
120
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
121
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
122
+ const persistedArchive = reloadedCore.listArchive(workspaceId);
123
+ const archivedTask = persistedArchive.archived.find((item) => item.id === 'task-cloud-archived-1');
124
+ const defaultWorkspaceArchive = reloadedCore.listArchive('default').archived;
125
+ expect(archivedTask).toBeTruthy();
126
+ expect(archivedTask?.title).toBe('Archived from cloud');
127
+ expect(archivedTask?.isArchived).toBe(true);
128
+ expect(defaultWorkspaceArchive.find((item) => item.id === 'task-cloud-archived-1')).toBeUndefined();
129
+ });
130
+ it('applies cloud delete operations to local sqlite across reload via apply-local route', async () => {
131
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-delete-');
132
+ const seedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
133
+ const seeded = seedCore.createTask({
134
+ title: 'Will be deleted by cloud sync',
135
+ category: 'Default',
136
+ type: 'feature',
137
+ priority: 2,
138
+ status: 'task'
139
+ }, { workspaceId });
140
+ expect(seeded.id).toBeTruthy();
141
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
142
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
143
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
144
+ expect(match).toBeTruthy();
145
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
146
+ changes: [
147
+ { op: 'delete', taskId: seeded.id }
148
+ ]
149
+ }, { 'x-taskforce-workspace-id': workspaceId });
150
+ const res = createMockRes();
151
+ await match?.handler(req, res, {});
152
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
153
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
154
+ const persisted = reloadedCore.listTasks(workspaceId);
155
+ const deletedTask = persisted.tasks.find((item) => item.id === seeded.id);
156
+ expect(deletedTask).toBeUndefined();
157
+ });
158
+ it('keeps task deleted when apply-local receives same-task upsert and delete in one batch', async () => {
159
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-delete-wins-');
160
+ const seedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
161
+ const seeded = seedCore.createTask({
162
+ title: 'Seed task for delete-wins',
163
+ category: 'Default',
164
+ type: 'feature',
165
+ priority: 2,
166
+ status: 'task'
167
+ }, { workspaceId });
168
+ expect(seeded.id).toBeTruthy();
169
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
170
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
171
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
172
+ expect(match).toBeTruthy();
173
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
174
+ changes: [
175
+ {
176
+ op: 'upsert',
177
+ archived: false,
178
+ task: {
179
+ id: seeded.id,
180
+ title: 'Stale cloud upsert'
181
+ }
182
+ },
183
+ { op: 'delete', taskId: seeded.id }
184
+ ]
185
+ }, { 'x-taskforce-workspace-id': workspaceId });
186
+ const res = createMockRes();
187
+ await match?.handler(req, res, {});
188
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
189
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
190
+ const persisted = reloadedCore.listTasks(workspaceId);
191
+ const shouldRemainDeleted = persisted.tasks.find((item) => item.id === seeded.id);
192
+ expect(shouldRemainDeleted).toBeUndefined();
193
+ });
194
+ it('applies document-delete operations by deleting file and clearing watermark index', async () => {
195
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-doc-delete-');
196
+ const seedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
197
+ seedCore.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
198
+ const syncRoot = path.join(seedCore.getPaths().basePath, '.taskforce');
199
+ const docsDir = path.join(syncRoot, 'docs');
200
+ fs.mkdirSync(docsDir, { recursive: true });
201
+ const relativeDocPath = 'docs/sync-delete.md';
202
+ const absoluteDocPath = path.join(syncRoot, relativeDocPath);
203
+ fs.writeFileSync(absoluteDocPath, '# sync delete\n', 'utf8');
204
+ seedCore.upsertDocumentWatermark(workspaceId, relativeDocPath, 'seed-hash', '2026-02-23T00:00:00.000Z');
205
+ expect(seedCore.getDocumentWatermark(workspaceId, relativeDocPath)).toBeTruthy();
206
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
207
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
208
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
209
+ expect(match).toBeTruthy();
210
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
211
+ changes: [
212
+ { op: 'document-delete', path: relativeDocPath }
213
+ ]
214
+ }, { 'x-taskforce-workspace-id': workspaceId });
215
+ const res = createMockRes();
216
+ await match?.handler(req, res, {});
217
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
218
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
219
+ expect(fs.existsSync(absoluteDocPath)).toBe(false);
220
+ expect(reloadedCore.getDocumentWatermark(workspaceId, relativeDocPath)).toBeNull();
221
+ });
222
+ it('propagates document-delete through authenticated push and omits deleted doc from pull payload', async () => {
223
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-push-pull-doc-delete-');
224
+ const seedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
225
+ const now = new Date().toISOString();
226
+ seedCore.db.prepare('INSERT OR IGNORE INTO users (id, tenant_id, email, created_at, updated_at) VALUES (?, ?, ?, ?, ?)').run('integration-user', 'default', 'integration@test.com', now, now);
227
+ seedCore.createWorkspaceForUser({
228
+ userId: 'integration-user',
229
+ name: 'Local Active Workspace',
230
+ role: 'owner',
231
+ preferredWorkspaceId: workspaceId
232
+ });
233
+ const syncRoot = path.join(seedCore.getPaths().basePath, '.taskforce');
234
+ const docsDir = path.join(syncRoot, 'docs');
235
+ fs.mkdirSync(docsDir, { recursive: true });
236
+ const relativeDocPath = 'docs/sync-delete-push.md';
237
+ const absoluteDocPath = path.join(syncRoot, relativeDocPath);
238
+ fs.writeFileSync(absoluteDocPath, '# from local\n', 'utf8');
239
+ seedCore.upsertDocumentWatermark(workspaceId, relativeDocPath, 'seed-hash', '2026-02-23T00:00:00.000Z');
240
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
241
+ const routes = createRoutes(core, { authConfig });
242
+ const pushMatch = matchRoute('POST', '/api/taskforce/sync/workspace/push', routes);
243
+ const pullMatch = matchRoute('GET', '/api/taskforce/sync/workspace/pull', routes);
244
+ expect(pushMatch).toBeTruthy();
245
+ expect(pullMatch).toBeTruthy();
246
+ const cookie = createAuthCookie(workspaceId);
247
+ const pushReq = createMockReq('POST', '/api/taskforce/sync/workspace/push', {
248
+ workspaceId,
249
+ idempotencyKey: 'doc-delete-integration-1',
250
+ changes: [
251
+ { op: 'document-delete', path: relativeDocPath }
252
+ ]
253
+ }, { cookie });
254
+ const pushRes = createMockRes();
255
+ await pushMatch?.handler(pushReq, pushRes, {});
256
+ expect(pushRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
257
+ const reloadedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
258
+ expect(fs.existsSync(absoluteDocPath)).toBe(false);
259
+ expect(reloadedCore.getDocumentWatermark(workspaceId, relativeDocPath)).toBeNull();
260
+ const pullReq = createMockReq('GET', '/api/taskforce/sync/workspace/pull?workspaceId=workspace-local-active&limit=200', undefined, { cookie });
261
+ const pullRes = createMockRes();
262
+ await pullMatch?.handler(pullReq, pullRes, {});
263
+ expect(pullRes.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
264
+ const pullBody = readJsonBody(pullRes);
265
+ const documentUpserts = (Array.isArray(pullBody?.changes) ? pullBody.changes : [])
266
+ .filter((change) => String(change?.op || '').toLowerCase() === 'document-upsert');
267
+ expect(documentUpserts.find((change) => change.path === relativeDocPath)).toBeUndefined();
268
+ });
269
+ it('keeps document deleted when push batch includes upsert and delete for same path', async () => {
270
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-push-doc-delete-wins-');
271
+ const seedCore = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
272
+ const now = new Date().toISOString();
273
+ seedCore.db.prepare('INSERT OR IGNORE INTO users (id, tenant_id, email, created_at, updated_at) VALUES (?, ?, ?, ?, ?)').run('integration-user', 'default', 'integration@test.com', now, now);
274
+ seedCore.createWorkspaceForUser({
275
+ userId: 'integration-user',
276
+ name: 'Local Active Workspace',
277
+ role: 'owner',
278
+ preferredWorkspaceId: workspaceId
279
+ });
280
+ const syncRoot = path.join(seedCore.getPaths().basePath, '.taskforce');
281
+ const docsDir = path.join(syncRoot, 'docs');
282
+ fs.mkdirSync(docsDir, { recursive: true });
283
+ const relativeDocPath = 'docs/sync-delete-wins.md';
284
+ const absoluteDocPath = path.join(syncRoot, relativeDocPath);
285
+ fs.writeFileSync(absoluteDocPath, '# old\n', 'utf8');
286
+ seedCore.upsertDocumentWatermark(workspaceId, relativeDocPath, 'seed-hash', '2026-02-23T00:00:00.000Z');
287
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
288
+ const routes = createRoutes(core, { authConfig });
289
+ const pushMatch = matchRoute('POST', '/api/taskforce/sync/workspace/push', routes);
290
+ expect(pushMatch).toBeTruthy();
291
+ const cookie = createAuthCookie(workspaceId);
292
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/push', {
293
+ workspaceId,
294
+ idempotencyKey: 'doc-delete-integration-2',
295
+ changes: [
296
+ {
297
+ op: 'document-upsert',
298
+ path: relativeDocPath,
299
+ updatedAt: '2026-02-23T10:00:00.000Z',
300
+ content: '# stale upsert'
301
+ },
302
+ {
303
+ op: 'document-delete',
304
+ path: relativeDocPath
305
+ }
306
+ ]
307
+ }, { cookie });
308
+ const res = createMockRes();
309
+ await pushMatch?.handler(req, res, {});
310
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
311
+ expect(fs.existsSync(absoluteDocPath)).toBe(false);
312
+ expect(core.getDocumentWatermark(workspaceId, relativeDocPath)).toBeNull();
313
+ });
314
+ it('returns 401 on apply-local when auth is enabled but absent', async () => {
315
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-auth-');
316
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
317
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
318
+ // Enable auth this time as we are testing it
319
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: true } });
320
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
321
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', { changes: [] }, { 'x-taskforce-workspace-id': workspaceId }
322
+ // No auth cookie or session
323
+ );
324
+ const res = createMockRes();
325
+ await match?.handler(req, res, {});
326
+ expect(res.writeHead).toHaveBeenCalledWith(401, expect.any(Object));
327
+ });
328
+ it('returns 400 on apply-local when workspaceId is null/empty/default', async () => {
329
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-apply-wsmissing-');
330
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
331
+ // Use false for auth to isolate workspaceId testing
332
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
333
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
334
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', { changes: [] }
335
+ // Omit custom headers, and the body does not have workspaceId
336
+ );
337
+ const res = createMockRes();
338
+ await match?.handler(req, res, {});
339
+ expect(res.writeHead).toHaveBeenCalledWith(400, expect.any(Object));
340
+ });
341
+ it('keeps repeated document scans correct and invalidates cache when file content changes', async () => {
342
+ const projectRoot = createIsolatedProjectRoot('taskforce-sync-doc-cache-');
343
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
344
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
345
+ const syncRoot = path.join(core.getPaths().basePath, '.taskforce');
346
+ const docsDir = path.join(syncRoot, 'docs');
347
+ fs.mkdirSync(docsDir, { recursive: true });
348
+ const relativeDocPath = 'docs/cache-target.md';
349
+ const absoluteDocPath = path.join(syncRoot, relativeDocPath);
350
+ fs.writeFileSync(absoluteDocPath, '# cache test\n', 'utf8');
351
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
352
+ const match = matchRoute('GET', '/api/taskforce/sync/workspace/documents', routes);
353
+ expect(match).toBeTruthy();
354
+ const req1 = createMockReq('GET', '/api/taskforce/sync/workspace/documents');
355
+ const res1 = createMockRes();
356
+ await match?.handler(req1, res1, {});
357
+ expect(res1.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
358
+ const body1 = JSON.parse(String(res1.end.mock.calls.at(-1)?.[0] || '{}'));
359
+ const req2 = createMockReq('GET', '/api/taskforce/sync/workspace/documents');
360
+ const res2 = createMockRes();
361
+ await match?.handler(req2, res2, {});
362
+ expect(res2.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
363
+ const body2 = JSON.parse(String(res2.end.mock.calls.at(-1)?.[0] || '{}'));
364
+ const firstDoc1 = (Array.isArray(body1?.documents) ? body1.documents : []).find((doc) => doc.path === relativeDocPath);
365
+ const firstDoc2 = (Array.isArray(body2?.documents) ? body2.documents : []).find((doc) => doc.path === relativeDocPath);
366
+ expect(firstDoc1?.content).toBe('# cache test\n');
367
+ expect(firstDoc2?.content).toBe('# cache test\n');
368
+ expect(firstDoc2?.updatedAt).toBe(firstDoc1?.updatedAt);
369
+ fs.writeFileSync(absoluteDocPath, '# cache test changed\n', 'utf8');
370
+ const req3 = createMockReq('GET', '/api/taskforce/sync/workspace/documents');
371
+ const res3 = createMockRes();
372
+ await match?.handler(req3, res3, {});
373
+ expect(res3.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
374
+ const body3 = JSON.parse(String(res3.end.mock.calls.at(-1)?.[0] || '{}'));
375
+ const firstDoc3 = (Array.isArray(body3?.documents) ? body3.documents : []).find((doc) => doc.path === relativeDocPath);
376
+ expect(firstDoc3?.content).toBe('# cache test changed\n');
377
+ });
378
+ it('converges task conflicts deterministically for equal timestamps regardless of apply order', async () => {
379
+ const applySequence = async (prefix, titlesInOrder) => {
380
+ const projectRoot = createIsolatedProjectRoot(prefix);
381
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
382
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
383
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
384
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
385
+ expect(match).toBeTruthy();
386
+ for (const title of titlesInOrder) {
387
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
388
+ changes: [{
389
+ op: 'upsert',
390
+ archived: false,
391
+ task: {
392
+ id: 'task-concurrent-1',
393
+ title,
394
+ status: 'task',
395
+ createdAt: '2026-02-27T00:00:00.000Z',
396
+ updatedAt: '2026-02-27T00:00:00.000Z'
397
+ }
398
+ }]
399
+ }, { 'x-taskforce-workspace-id': workspaceId });
400
+ const res = createMockRes();
401
+ await match?.handler(req, res, {});
402
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
403
+ }
404
+ return core.getTask('task-concurrent-1', workspaceId)?.title || null;
405
+ };
406
+ const orderAB = await applySequence('taskforce-sync-conflict-task-ab-', ['alpha-title', 'zeta-title']);
407
+ const orderBA = await applySequence('taskforce-sync-conflict-task-ba-', ['zeta-title', 'alpha-title']);
408
+ expect(orderAB).toBeTruthy();
409
+ expect(orderBA).toBe(orderAB);
410
+ });
411
+ it('converges document metadata conflicts deterministically for equal timestamps regardless of apply order', async () => {
412
+ const applySequence = async (prefix, contentsInOrder) => {
413
+ const projectRoot = createIsolatedProjectRoot(prefix);
414
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
415
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
416
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
417
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
418
+ expect(match).toBeTruthy();
419
+ const relativeDocPath = 'docs/conflict-doc.md';
420
+ const absoluteDocPath = path.join(core.getPaths().basePath, '.taskforce', relativeDocPath);
421
+ for (const content of contentsInOrder) {
422
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
423
+ changes: [{
424
+ op: 'document-upsert',
425
+ path: relativeDocPath,
426
+ updatedAt: '2026-02-27T00:00:00.000Z',
427
+ content
428
+ }]
429
+ }, { 'x-taskforce-workspace-id': workspaceId });
430
+ const res = createMockRes();
431
+ await match?.handler(req, res, {});
432
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
433
+ }
434
+ return fs.existsSync(absoluteDocPath) ? fs.readFileSync(absoluteDocPath, 'utf8') : '';
435
+ };
436
+ const orderAB = await applySequence('taskforce-sync-conflict-doc-ab-', ['doc-content-alpha', 'doc-content-zeta']);
437
+ const orderBA = await applySequence('taskforce-sync-conflict-doc-ba-', ['doc-content-zeta', 'doc-content-alpha']);
438
+ expect(orderAB).toBeTruthy();
439
+ expect(orderBA).toBe(orderAB);
440
+ });
441
+ it('converges taxonomy conflicts deterministically for equal timestamps regardless of apply order', async () => {
442
+ const applySequence = async (prefix, taxonomiesInOrder) => {
443
+ const projectRoot = createIsolatedProjectRoot(prefix);
444
+ const core = new TaskforceCore({ projectRoot, storagePath: '.taskforce' });
445
+ core.createWorkspace({ workspaceId, name: 'Local Active Workspace' });
446
+ const routes = createRoutes(core, { authConfig: { runtimeMode: 'local', enabled: false } });
447
+ const match = matchRoute('POST', '/api/taskforce/sync/workspace/apply-local', routes);
448
+ expect(match).toBeTruthy();
449
+ for (const taxonomies of taxonomiesInOrder) {
450
+ const req = createMockReq('POST', '/api/taskforce/sync/workspace/apply-local', {
451
+ changes: [{
452
+ op: 'taxonomy-upsert',
453
+ updatedAt: '2026-02-27T00:00:00.000Z',
454
+ taxonomies
455
+ }]
456
+ }, { 'x-taskforce-workspace-id': workspaceId });
457
+ const res = createMockRes();
458
+ await match?.handler(req, res, {});
459
+ expect(res.writeHead).toHaveBeenCalledWith(200, expect.any(Object));
460
+ }
461
+ return core.getTaxonomies();
462
+ };
463
+ const alphaTaxonomies = [{ id: 'custom', label: 'Alpha', options: [], widgetType: 'select', isSystem: false, multiSelect: false }];
464
+ const zetaTaxonomies = [{ id: 'custom', label: 'Zeta', options: [], widgetType: 'select', isSystem: false, multiSelect: false }];
465
+ const orderAB = await applySequence('taskforce-sync-conflict-tax-ab-', [alphaTaxonomies, zetaTaxonomies]);
466
+ const orderBA = await applySequence('taskforce-sync-conflict-tax-ba-', [zetaTaxonomies, alphaTaxonomies]);
467
+ expect(Array.isArray(orderAB)).toBe(true);
468
+ expect(orderBA).toEqual(orderAB);
469
+ });
470
+ });