@@ -278,3 +278,67 @@ test('two Frames arguing over one spec stop being answered', () => {
278278 assert . ok ( corrected <= 3 , `and then stops answering (corrected ${ corrected } times)` ) ;
279279 assert . equal ( phaseNow ( ) , 'done' , 'the last write is the other Frame\'s, not ours' ) ;
280280} ) ;
281+
282+ // ─── first import starts from what the spec records ─────────
283+
284+ function setStatus ( fields ) {
285+ const statusPath = path . join ( specDir ( ) , 'status.json' ) ;
286+ const s = JSON . parse ( fs . readFileSync ( statusPath , 'utf8' ) ) ;
287+ fs . writeFileSync ( statusPath , JSON . stringify ( { ...s , ...fields } , null , 2 ) , 'utf8' ) ;
288+ }
289+
290+ test ( 'a finished spec arriving by merge is imported completed, not walked back' , ( ) => {
291+ // Implemented in a worktree: its tasks were completed in a tasks.json nobody
292+ // commits, so main gets tasks.md and a `done` status.json but no tasks.
293+ // Importing them as pending used to rewind the spec to tasks_generated.
294+ writeStatus ( ) ;
295+ writeTasksMd ( NON_ASCENDING ) ;
296+ setStatus ( { phase : 'done' , last_phase_at : '2026-10-08T13:29:41.859Z' } ) ;
297+
298+ specManager . syncTasksFromMarkdown ( projectDir , SLUG ) ;
299+
300+ const tasks = readSpecTasks ( ) ;
301+ assert . equal ( tasks . length , 6 ) ;
302+ assert . ok ( tasks . every ( t => t . status === 'completed' ) , 'every task imported completed' ) ;
303+ assert . ok ( tasks . every ( t => t . completedAt === '2026-10-08T13:29:41.859Z' ) , 'completedAt is when the spec finished' ) ;
304+
305+ specManager . reconcilePhase ( projectDir , SLUG , tasksManager . loadTasks ( projectDir ) ) ;
306+ assert . equal ( JSON . parse ( fs . readFileSync ( path . join ( specDir ( ) , 'status.json' ) , 'utf8' ) ) . phase , 'done' ) ;
307+ } ) ;
308+
309+ test ( 'an unfinished spec imports the tasks its outcome.md records as completed' , ( ) => {
310+ writeStatus ( ) ;
311+ writeTasksMd ( NON_ASCENDING ) ;
312+ setStatus ( { phase : 'implementing' } ) ;
313+ fs . writeFileSync ( path . join ( specDir ( ) , 'outcome.md' ) , [
314+ '## T01 — Expose the report path' , '' , 'Did it.' , '' , '---' , '' ,
315+ '## T05 — Handle a missing report' , '' , 'Did it. Mentions T03 in passing.' , '' , '---' , ''
316+ ] . join ( '\n' ) , 'utf8' ) ;
317+
318+ specManager . syncTasksFromMarkdown ( projectDir , SLUG ) ;
319+
320+ const status = ( id ) => readSpecTasks ( ) . find ( t => t . source === `spec:${ SLUG } :${ id } ` ) . status ;
321+ assert . equal ( status ( 'T01' ) , 'completed' ) ;
322+ assert . equal ( status ( 'T05' ) , 'completed' ) ;
323+ assert . equal ( status ( 'T03' ) , 'pending' , 'a task only mentioned in prose is not completed' ) ;
324+ assert . equal ( status ( 'T02' ) , 'pending' ) ;
325+ assert . equal (
326+ specManager . derivePhase ( projectDir , SLUG , 'implementing' , tasksManager . loadTasks ( projectDir ) ) ,
327+ 'implementing'
328+ ) ;
329+ } ) ;
330+
331+ test ( 'the initial state applies to first import only — user-set status still wins' , ( ) => {
332+ writeStatus ( ) ;
333+ writeTasksMd ( NON_ASCENDING ) ;
334+ setStatus ( { phase : 'done' } ) ;
335+ specManager . syncTasksFromMarkdown ( projectDir , SLUG ) ;
336+
337+ // The user reopens a task.
338+ const data = tasksManager . loadTasks ( projectDir ) ;
339+ data . tasks . find ( t => t . source === `spec:${ SLUG } :T03` ) . status = 'pending' ;
340+ tasksManager . saveTasks ( projectDir , data ) ;
341+
342+ specManager . syncTasksFromMarkdown ( projectDir , SLUG ) ;
343+ assert . equal ( readSpecTasks ( ) . find ( t => t . source === `spec:${ SLUG } :T03` ) . status , 'pending' ) ;
344+ } ) ;
0 commit comments