@@ -146,6 +146,91 @@ describe("importRooTaskHistory", () => {
146146 } )
147147 } )
148148
149+ it ( "does not overwrite an existing Zoo task directory when the same Roo history is imported again" , async ( ) => {
150+ const zooGlobalStoragePath = path . join ( tempRoot , "globalStorage" , "zoocodeorganization.zoo-code" )
151+ const rooDefaultStorageRoot = path . join ( tempRoot , "globalStorage" , "rooveterinaryinc.roo-cline" )
152+
153+ mockStorageConfiguration ( )
154+
155+ await fs . mkdir ( path . join ( rooDefaultStorageRoot , "tasks" , "task-repeat" ) , { recursive : true } )
156+ await fs . writeFile (
157+ path . join ( rooDefaultStorageRoot , "tasks" , "task-repeat" , "history_item.json" ) ,
158+ JSON . stringify ( { id : "task-repeat" , source : "first-import" } ) ,
159+ )
160+ await fs . writeFile ( path . join ( rooDefaultStorageRoot , "tasks" , "task-repeat" , "ui_messages.json" ) , "first-ui" )
161+
162+ const firstImportResult = await importRooTaskHistory ( zooGlobalStoragePath )
163+
164+ expect ( firstImportResult . importedTaskCount ) . toBe ( 1 )
165+ expect ( firstImportResult . importedFileCount ) . toBe ( 2 )
166+
167+ await fs . writeFile (
168+ path . join ( rooDefaultStorageRoot , "tasks" , "task-repeat" , "history_item.json" ) ,
169+ JSON . stringify ( { id : "task-repeat" , source : "second-import" } ) ,
170+ )
171+ await fs . writeFile ( path . join ( rooDefaultStorageRoot , "tasks" , "task-repeat" , "ui_messages.json" ) , "second-ui" )
172+
173+ const secondImportResult = await importRooTaskHistory ( zooGlobalStoragePath )
174+
175+ expect ( secondImportResult . importedTaskCount ) . toBe ( 0 )
176+ expect ( secondImportResult . importedFileCount ) . toBe ( 0 )
177+ expect (
178+ await fs . readFile ( path . join ( zooGlobalStoragePath , "tasks" , "task-repeat" , "history_item.json" ) , "utf8" ) ,
179+ ) . toBe ( JSON . stringify ( { id : "task-repeat" , source : "first-import" } ) )
180+ expect (
181+ await fs . readFile ( path . join ( zooGlobalStoragePath , "tasks" , "task-repeat" , "ui_messages.json" ) , "utf8" ) ,
182+ ) . toBe ( "first-ui" )
183+ } )
184+
185+ it ( "deterministically keeps the first importable Roo task when duplicate task IDs exist across roots" , async ( ) => {
186+ const zooGlobalStoragePath = path . join ( tempRoot , "globalStorage" , "zoocodeorganization.zoo-code" )
187+ const rooDefaultStorageRoot = path . join ( tempRoot , "globalStorage" , "rooveterinaryinc.roo-cline" )
188+ const rooCustomStorageRoot = path . join ( tempRoot , "roo-custom" )
189+
190+ mockStorageConfiguration ( { roo : rooCustomStorageRoot } )
191+
192+ await fs . mkdir ( path . join ( rooDefaultStorageRoot , "tasks" , "task-shared" ) , { recursive : true } )
193+ await fs . writeFile (
194+ path . join ( rooDefaultStorageRoot , "tasks" , "task-shared" , "history_item.json" ) ,
195+ JSON . stringify ( { id : "task-shared" , source : "default-root" } ) ,
196+ )
197+ await fs . writeFile ( path . join ( rooDefaultStorageRoot , "tasks" , "task-shared" , "ui_messages.json" ) , "default-ui" )
198+
199+ await fs . mkdir ( path . join ( rooCustomStorageRoot , "tasks" , "task-shared" ) , { recursive : true } )
200+ await fs . writeFile (
201+ path . join ( rooCustomStorageRoot , "tasks" , "task-shared" , "history_item.json" ) ,
202+ JSON . stringify ( { id : "task-shared" , source : "custom-root" } ) ,
203+ )
204+ await fs . writeFile ( path . join ( rooCustomStorageRoot , "tasks" , "task-shared" , "ui_messages.json" ) , "custom-ui" )
205+
206+ await fs . mkdir ( path . join ( rooCustomStorageRoot , "tasks" , "task-custom-only" ) , { recursive : true } )
207+ await fs . writeFile (
208+ path . join ( rooCustomStorageRoot , "tasks" , "task-custom-only" , "history_item.json" ) ,
209+ JSON . stringify ( { id : "task-custom-only" , source : "custom-root" } ) ,
210+ )
211+ await fs . writeFile (
212+ path . join ( rooCustomStorageRoot , "tasks" , "task-custom-only" , "ui_messages.json" ) ,
213+ "custom-only-ui" ,
214+ )
215+
216+ const result = await importRooTaskHistory ( zooGlobalStoragePath )
217+
218+ expect ( result . importedTaskCount ) . toBe ( 2 )
219+ expect ( result . importedFileCount ) . toBe ( 4 )
220+ expect (
221+ await fs . readFile ( path . join ( zooGlobalStoragePath , "tasks" , "task-shared" , "history_item.json" ) , "utf8" ) ,
222+ ) . toBe ( JSON . stringify ( { id : "task-shared" , source : "default-root" } ) )
223+ expect (
224+ await fs . readFile ( path . join ( zooGlobalStoragePath , "tasks" , "task-shared" , "ui_messages.json" ) , "utf8" ) ,
225+ ) . toBe ( "default-ui" )
226+ expect (
227+ await fs . readFile (
228+ path . join ( zooGlobalStoragePath , "tasks" , "task-custom-only" , "history_item.json" ) ,
229+ "utf8" ,
230+ ) ,
231+ ) . toBe ( JSON . stringify ( { id : "task-custom-only" , source : "custom-root" } ) )
232+ } )
233+
149234 it ( "reports Roo history import progress as files are copied" , async ( ) => {
150235 const zooGlobalStoragePath = path . join ( tempRoot , "globalStorage" , "zoocodeorganization.zoo-code" )
151236 const rooDefaultStorageRoot = path . join ( tempRoot , "globalStorage" , "rooveterinaryinc.roo-cline" )
@@ -340,6 +425,31 @@ describe("importRooTaskHistory", () => {
340425 expect ( await fs . readFile ( path . join ( existingZooTaskDirectory , "history_item.json" ) , "utf8" ) ) . toBe ( "existing" )
341426 } )
342427
428+ it ( "does not overwrite an existing Zoo task when the Roo task is otherwise importable" , async ( ) => {
429+ const zooGlobalStoragePath = path . join ( tempRoot , "globalStorage" , "zoocodeorganization.zoo-code" )
430+ const rooDefaultStorageRoot = path . join ( tempRoot , "globalStorage" , "rooveterinaryinc.roo-cline" )
431+ const existingZooTaskDirectory = path . join ( zooGlobalStoragePath , "tasks" , "task-existing" )
432+
433+ mockStorageConfiguration ( )
434+
435+ await fs . mkdir ( path . join ( rooDefaultStorageRoot , "tasks" , "task-existing" ) , { recursive : true } )
436+ await fs . writeFile (
437+ path . join ( rooDefaultStorageRoot , "tasks" , "task-existing" , "history_item.json" ) ,
438+ JSON . stringify ( { id : "task-existing" , source : "roo" } ) ,
439+ )
440+ await fs . writeFile ( path . join ( rooDefaultStorageRoot , "tasks" , "task-existing" , "ui_messages.json" ) , "roo-ui" )
441+ await fs . mkdir ( existingZooTaskDirectory , { recursive : true } )
442+ await fs . writeFile ( path . join ( existingZooTaskDirectory , "history_item.json" ) , "existing" )
443+ await fs . writeFile ( path . join ( existingZooTaskDirectory , "ui_messages.json" ) , "existing-ui" )
444+
445+ const result = await importRooTaskHistory ( zooGlobalStoragePath )
446+
447+ expect ( result . importedTaskCount ) . toBe ( 0 )
448+ expect ( result . importedFileCount ) . toBe ( 0 )
449+ expect ( await fs . readFile ( path . join ( existingZooTaskDirectory , "history_item.json" ) , "utf8" ) ) . toBe ( "existing" )
450+ expect ( await fs . readFile ( path . join ( existingZooTaskDirectory , "ui_messages.json" ) , "utf8" ) ) . toBe ( "existing-ui" )
451+ } )
452+
343453 it ( "rethrows unexpected task-root errors while importing Roo history" , async ( ) => {
344454 const zooGlobalStoragePath = path . join ( tempRoot , "globalStorage" , "zoocodeorganization.zoo-code" )
345455 const rooDefaultStorageRoot = path . join ( tempRoot , "globalStorage" , "rooveterinaryinc.roo-cline" )
0 commit comments